MDL-54687 core_message: added search functionality

This commit is contained in:
Mark Nelson
2016-10-07 16:26:40 +08:00
parent d29cdf3a1f
commit cd03b8d7f2
16 changed files with 1181 additions and 27 deletions
+2
View File
@@ -114,6 +114,7 @@ $string['nomessages'] = 'No messages waiting';
$string['nomessagesfound'] = 'No messages were found';
$string['noreply'] = 'Do not reply to this message';
$string['nosearchresults'] = 'There were no results from your search';
$string['noncontacts'] = 'Non-contacts';
$string['nonotifications'] = 'You have no notifications';
$string['nonewnotifications'] = 'You have no new notifications';
$string['notificationwindow'] = 'Notification window';
@@ -149,6 +150,7 @@ $string['removefromyourcontacts'] = 'Remove from your contacts';
$string['savemysettings'] = 'Save my settings';
$string['search'] = 'Search';
$string['searchforperson'] = 'Search for a person';
$string['searchforpersonorcourse'] = 'Search for a person or course';
$string['searchmessages'] = 'Search messages';
$string['searchcombined'] = 'Search people and messages';
$string['selectmessagestodelete'] = 'Select messages or conversations to delete';
+24
View File
@@ -651,6 +651,30 @@ $functions = array(
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_message_data_for_messagearea_search_messages' => array(
'classname' => 'core_message_external',
'methodname' => 'data_for_messagearea_search_messages',
'classpath' => 'message/externallib.php',
'description' => 'Retrieve the template data for searching for messages',
'type' => 'read',
'ajax' => true,
),
'core_message_data_for_messagearea_search_people' => array(
'classname' => 'core_message_external',
'methodname' => 'data_for_messagearea_search_people',
'classpath' => 'message/externallib.php',
'description' => 'Retrieve the template data for searching for people',
'type' => 'read',
'ajax' => true,
),
'core_message_data_for_messagearea_search_people_in_course' => array(
'classname' => 'core_message_external',
'methodname' => 'data_for_messagearea_search_people_in_course',
'classpath' => 'message/externallib.php',
'description' => 'Retrieve the template data for searching for people in a course',
'type' => 'read',
'ajax' => true,
),
'core_message_data_for_messagearea_conversations' => array(
'classname' => 'core_message_external',
'methodname' => 'data_for_messagearea_conversations',
+11 -4
View File
@@ -22,9 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core_message/message_area_contacts', 'core_message/message_area_messages',
'core_message/message_area_profile', 'core_message/message_area_tabs',
'core_message/message_area_actions'],
function($, Contacts, Messages, Profile, Tabs, Actions) {
'core_message/message_area_profile', 'core_message/message_area_tabs', 'core_message/message_area_search'],
function($, Contacts, Messages, Profile, Tabs, Search) {
/**
* Messagearea class.
@@ -48,6 +47,7 @@ define(['jquery', 'core_message/message_area_contacts', 'core_message/message_ar
CONVERSATIONS: "[data-region='contacts'][data-region-content='conversations']",
DELETEMESSAGES: "[data-action='delete-messages']",
DELETEMESSAGECHECKBOX: "[data-region='delete-message-checkbox']",
DELETESEARCHFILTER: "[data-action='search-filter-delete']",
LASTMESSAGE: '.lastmessage',
LOADINGICON: '.loading-icon',
MENU: "[data-region='menu']",
@@ -64,8 +64,12 @@ define(['jquery', 'core_message/message_area_contacts', 'core_message/message_ar
PROFILESENDMESSAGE: "[data-action='profile-send-message']",
PROFILEUNBLOCKCONTACT: "[data-action='profile-unblock-contact']",
PROFILEVIEW: "[data-action='profile-view']",
SEARCHAREA: "[data-region='search-area']",
SEARCHBOX: "[data-region='search-box']",
SEARCHFILTER: "[data-region='search-filter']",
SEARCHFILTERAREA: "[data-region='search-filter-area']",
SEARCHPEOPLEINCOURSE : "[data-action='search-people-in-course']",
SEARCHRESULTSAREA: "[data-region='search-results-area']",
SEARCHTEXTAREA: "[data-region='search-text-area']",
SELECTEDVIEWPROFILE: "[data-action='view-contact-profile'].selected",
SELECTEDVIEWCONVERSATION: "[data-action='view-contact-msg'].selected",
SENDMESSAGE: "[data-action='send-message']",
@@ -91,7 +95,9 @@ define(['jquery', 'core_message/message_area_contacts', 'core_message/message_ar
CONVERSATIONSELECTED: 'conversation-selected',
CONVERSATIONSSELECTED: 'conversations-selected',
MESSAGESDELETED: 'messages-deleted',
MESSAGESEARCHCANCELED: 'message-search-canceled',
MESSAGESENT: 'message-sent',
PEOPLESEARCHCANCELED: 'people-search-canceled',
SENDMESSAGE: 'message-send'
};
@@ -106,6 +112,7 @@ define(['jquery', 'core_message/message_area_contacts', 'core_message/message_ar
new Messages(this);
new Profile(this);
new Tabs(this);
new Search(this);
};
/**
+19 -8
View File
@@ -73,6 +73,9 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
customEvents.events.up,
]);
this.messageArea.onCustomEvent(this.messageArea.EVENTS.MESSAGESEARCHCANCELED, this._viewConversations.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.PEOPLESEARCHCANCELED, this._viewContacts.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONTACTSSELECTED, this._viewContacts.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONSSELECTED, this._viewConversations.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONTACTSSELECTED, this._viewContacts.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.MESSAGESDELETED, this._deleteConversations.bind(this));
@@ -180,7 +183,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
// Replace the text.
user.find(this.messageArea.SELECTORS.LASTMESSAGE).empty().append(text);
// Ensure user is selected.
this._setSelectedUser(userid);
this._setSelectedUser("[data-userid='" + userid + "']");
};
/**
@@ -282,7 +285,15 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
Contacts.prototype._viewConversation = function(event) {
if (!this._isDeleting) {
var userid = $(event.currentTarget).data('userid');
this._setSelectedUser(userid);
var messageid = $(event.currentTarget).data('messageid');
var selector = "[data-userid='" + userid + "']";
// If we have a specific message id then we did a search and the contact may appear in multiple
// places - we don't want to highlight them all.
if (messageid) {
selector = "[data-messageid='" + messageid + "']";
}
this._setSelectedUser(selector);
this.messageArea.trigger(this.messageArea.EVENTS.CONVERSATIONSELECTED, userid);
// Don't highlight the contact because the message region has changed.
this.messageArea.find(this.messageArea.SELECTORS.SELECTEDVIEWPROFILE).removeClass('selected');
@@ -298,7 +309,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
Contacts.prototype._viewContact = function(event) {
if (!this._isDeleting) {
var userid = $(event.currentTarget).data('userid');
this._setSelectedUser(userid);
this._setSelectedUser("[data-userid='" + userid + "']");
this.messageArea.trigger(this.messageArea.EVENTS.CONTACTSELECTED, userid);
// Don't highlight the conversation because the message region has changed.
this.messageArea.find(this.messageArea.SELECTORS.SELECTEDVIEWCONVERSATION).removeClass('selected');
@@ -448,14 +459,14 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
/**
* Handles selecting a contact in the list.
*
* @param {int} userid
* @param {String} selector
* @private
*/
Contacts.prototype._setSelectedUser = function(userid) {
Contacts.prototype._setSelectedUser = function(selector) {
// Remove the 'selected' class from any other contact.
this.messageArea.find(this.messageArea.SELECTORS.CONTACT).removeClass('selected');
// Set the tab for the user to selected.
this.messageArea.find(this.messageArea.SELECTORS.CONTACT + "[data-userid='" + userid + "']").addClass('selected');
this.messageArea.find(this.messageArea.SELECTORS.CONTACT + selector).addClass('selected');
};
/**
@@ -536,14 +547,14 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/cust
* Flags the search area as seaching.
*/
Contacts.prototype._setSearching = function() {
$(this.messageArea.SELECTORS.SEARCHAREA).addClass('searching');
$(this.messageArea.SELECTORS.SEARCHTEXTAREA).addClass('searching');
};
/**
* Flags the search area as seaching.
*/
Contacts.prototype._clearSearching = function() {
$(this.messageArea.SELECTORS.SEARCHAREA).removeClass('searching');
$(this.messageArea.SELECTORS.SEARCHTEXTAREA).removeClass('searching');
};
/**
+387
View File
@@ -0,0 +1,387 @@
// 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/>.
/**
* The module handles searching contacts.
*
* @module core_message/message_area_search
* @package core_message
* @copyright 2016 Mark Nelson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/custom_interaction_events'],
function($, ajax, templates, notification, str, customEvents) {
/**
* Search class.
*
* @param {Messagearea} messageArea The messaging area object.
*/
function Search(messageArea) {
this.messageArea = messageArea;
this._init();
}
/** @type {Messagearea} The messaging area object. */
Search.prototype.messageArea = null;
/** @type {String} The area we are searching in. */
Search.prototype._searchArea = null;
/** @type {String} The id of the course we are searching in (if any). */
Search.prototype._courseid = null;
/** @type {Boolean} checks if we are currently loading */
Search.prototype._isLoading = false;
/** @type {String} The number of messages displayed. */
Search.prototype._numMessagesDisplayed = 0;
/** @type {String} The number of messages to retrieve. */
Search.prototype._numMessagesToRetrieve = 20;
/** @type {String} The number of people displayed. */
Search.prototype._numPeopleDisplayed = 0;
/** @type {String} The number of people to retrieve. */
Search.prototype._numPeopleToRetrieve = 20;
/** @type {Array} The type of available search areas. **/
Search.prototype._searchAreas = {
MESSAGES: 'messages',
PEOPLE: 'people',
PEOPLEINCOURSE: 'peopleincourse'
};
/** @type {int} The timeout before performing an ajax search */
Search.prototype._requestTimeout = null;
/**
* Initialise the event listeners.
*
* @private
*/
Search.prototype._init = function() {
// Handle searching for text.
this.messageArea.find(this.messageArea.SELECTORS.SEARCHTEXTAREA).on('input', this._searchRequest.bind(this));
// Handle clicking on a course in the list of people.
this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.SEARCHPEOPLEINCOURSE, function(e) {
this._setFilter($(e.currentTarget).html());
this._setPlaceholderText('searchforperson');
this._clearSearchArea();
this._searchArea = this._searchAreas.PEOPLEINCOURSE;
this._courseid = $(e.currentTarget).data('courseid');
this._searchPeopleInCourse();
}.bind(this));
// Handle deleting the search filter.
this.messageArea.onDelegateEvent('click', this.messageArea.SELECTORS.DELETESEARCHFILTER, function() {
this._hideSearchResults();
// Filter has been removed, so we don't want to be searching in a course anymore.
this._searchArea = this._searchAreas.PEOPLE;
this._setPlaceholderText('searchforpersonorcourse');
// Go back the contacts.
this.messageArea.trigger(this.messageArea.EVENTS.PEOPLESEARCHCANCELED);
}.bind(this));
// Handle events that occur outside this module.
this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONVERSATIONSSELECTED, function() {
this._hideSearchResults();
this._searchArea = this._searchAreas.MESSAGES;
this._setPlaceholderText('searchmessages');
}.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.CONTACTSSELECTED, function() {
this._hideSearchResults();
this._searchArea = this._searchAreas.PEOPLE;
this._setPlaceholderText('searchforpersonorcourse');
}.bind(this));
this.messageArea.onCustomEvent(this.messageArea.EVENTS.MESSAGESENT, function() {
this._hideSearchResults();
this._searchArea = this._searchAreas.MESSAGES;
this._setPlaceholderText('searchmessages');
}.bind(this));
// Event listeners for scrolling through messages and people in courses.
customEvents.define(this.messageArea.SELECTORS.SEARCHRESULTSAREA, [
customEvents.events.scrollBottom
]);
this.messageArea.onDelegateEvent(customEvents.events.scrollBottom, this.messageArea.SELECTORS.SEARCHRESULTSAREA,
function() {
if (this._searchArea == this._searchAreas.MESSAGES) {
this._searchMessages();
} else if (this._searchArea == this._searchAreas.PEOPLEINCOURSE) {
this._searchPeopleInCourse();
}
}.bind(this)
);
// Set the initial search area.
this._searchArea = this._searchAreas.MESSAGES;
};
/**
* Handles when search requests are sent.
*
* @private
*/
Search.prototype._searchRequest = function() {
var str = this.messageArea.find(this.messageArea.SELECTORS.SEARCHTEXTAREA + ' input').val();
if (this._requestTimeout) {
clearTimeout(this._requestTimeout);
}
if (str.trim() === '') {
// If nothing we being searched then we need to display the usual data.
if (this._searchArea == this._searchAreas.MESSAGES) {
this._hideSearchResults();
this.messageArea.trigger(this.messageArea.EVENTS.MESSAGESEARCHCANCELED);
} else if (this._searchArea == this._searchAreas.PEOPLE) {
this._hideSearchResults();
this.messageArea.trigger(this.messageArea.EVENTS.PEOPLESEARCHCANCELED);
} else if (this._searchArea == this._searchAreas.PEOPLEINCOURSE) {
// We are still searching in a course, so need to list all the people again.
this._clearSearchArea();
this._searchPeopleInCourse();
}
return;
}
this.messageArea.find(this.messageArea.SELECTORS.CONVERSATIONS).hide();
this.messageArea.find(this.messageArea.SELECTORS.CONTACTS).hide();
this.messageArea.find(this.messageArea.SELECTORS.SEARCHRESULTSAREA).show();
if (this._searchArea == this._searchAreas.MESSAGES) {
this._requestTimeout = setTimeout(function () {
this._clearSearchArea();
this._numMessagesDisplayed = 0;
this._searchMessages();
}.bind(this), 300);
} else if (this._searchArea == this._searchAreas.PEOPLEINCOURSE) {
this._requestTimeout = setTimeout(function () {
this._clearSearchArea();
this._numPeopleDisplayed = 0;
this._searchPeopleInCourse();
}.bind(this), 300);
} else { // Must be searching for people and courses
this._requestTimeout = setTimeout(function() {
this._clearSearchArea();
this._numPeopleDisplayed = 0;
this._searchPeople();
}.bind(this), 300);
}
};
/**
* Handles searching for messages.
*
* @private
* @returns {Promise} The promise resolved when the search area has been rendered
*/
Search.prototype._searchMessages = function() {
if (this._isLoading) {
return;
}
var str = this.messageArea.find(this.messageArea.SELECTORS.SEARCHBOX).val();
// Tell the user we are loading items.
this._isLoading = true;
// Call the web service to get our data.
var promises = ajax.call([{
methodname: 'core_message_data_for_messagearea_search_messages',
args: {
userid: this.messageArea.getCurrentUserId(),
search: str,
limitfrom: this._numMessagesDisplayed,
limitnum: this._numMessagesToRetrieve
}
}]);
// Keep track of the number of messages
var numberreceived = 0;
// Add loading icon to the end of the list.
return templates.render('core/loading', {}).then(function(html, js) {
templates.appendNodeContents(this.messageArea.SELECTORS.SEARCHRESULTSAREA,
"<div style='text-align:center'>" + html + "</div>", js);
return promises[0];
}.bind(this)).then(function(data) {
numberreceived = data.contacts.length;
return templates.render('core_message/message_area_contacts', data);
}).then(function(html, js) {
// Remove the loading icon.
this.messageArea.find(this.messageArea.SELECTORS.SEARCHRESULTSAREA + " " +
this.messageArea.SELECTORS.LOADINGICON).remove();
// Only append data if we got data back.
if (numberreceived > 0) {
// Show the new content.
templates.appendNodeContents(this.messageArea.SELECTORS.SEARCHRESULTSAREA, html, js);
// Increment the number of contacts displayed.
this._numMessagesDisplayed += numberreceived;
}
// Mark that we are no longer busy loading data.
this._isLoading = false;
}.bind(this)).fail(notification.exception);
};
/**
* Handles searching for people.
*
* @private
* @returns {Promise} The promise resolved when the search area has been rendered
*/
Search.prototype._searchPeople = function() {
var str = this.messageArea.find(this.messageArea.SELECTORS.SEARCHBOX).val();
// Call the web service to get our data.
var promises = ajax.call([{
methodname: 'core_message_data_for_messagearea_search_people',
args: {
userid: this.messageArea.getCurrentUserId(),
search: str,
limitnum: this._numPeopleToRetrieve
}
}]);
// Perform the search and replace the content.
return templates.render('core/loading', {}).then(function(html, js) {
templates.replaceNodeContents(this.messageArea.SELECTORS.SEARCHRESULTSAREA,
"<div style='text-align:center'>" + html + "</div>", js);
return promises[0];
}.bind(this)).then(function(data) {
return templates.render('core_message/message_area_people_search_results', data);
}).then(function(html, js) {
templates.replaceNodeContents(this.messageArea.SELECTORS.SEARCHRESULTSAREA, html, js);
}.bind(this)).fail(notification.exception);
};
/**
* Handles searching for people in a course.
*
* @private
* @returns {Promise} The promise resolved when the search area has been rendered
*/
Search.prototype._searchPeopleInCourse = function() {
if (this._isLoading) {
return;
}
var str = this.messageArea.find(this.messageArea.SELECTORS.SEARCHBOX).val();
// Tell the user we are loading items.
this._isLoading = true;
// Call the web service to get our data.
var promises = ajax.call([{
methodname: 'core_message_data_for_messagearea_search_people_in_course',
args: {
userid: this.messageArea.getCurrentUserId(),
courseid: this._courseid,
search: str,
limitfrom: this._numPeopleDisplayed,
limitnum: this._numPeopleToRetrieve
}
}]);
// Keep track of the number of contacts
var numberreceived = 0;
// Add loading icon to the end of the list.
return templates.render('core/loading', {}).then(function(html, js) {
templates.appendNodeContents(this.messageArea.SELECTORS.SEARCHRESULTSAREA,
"<div style='text-align:center'>" + html + "</div>", js);
return promises[0];
}.bind(this)).then(function(data) {
numberreceived = data.contacts.length;
return templates.render('core_message/message_area_people_search_results', data);
}).then(function(html, js) {
// Remove the loading icon.
this.messageArea.find(this.messageArea.SELECTORS.SEARCHRESULTSAREA + " " +
this.messageArea.SELECTORS.LOADINGICON).remove();
// Only append data if we got data back.
if (numberreceived > 0) {
// Show the new content.
templates.appendNodeContents(this.messageArea.SELECTORS.SEARCHRESULTSAREA, html, js);
// Increment the number of contacts displayed.
this._numPeopleDisplayed += numberreceived;
}
// Mark that we are no longer busy loading data.
this._isLoading = false;
}.bind(this)).fail(notification.exception);
};
/**
* Sets placeholder text for search input.
*
* @private
* @param {String} text The placeholder text
* @return {Promise} The promise resolved when the placeholder text has been set
*/
Search.prototype._setPlaceholderText = function(text) {
return str.get_string(text, 'message').then(function(s) {
this.messageArea.find(this.messageArea.SELECTORS.SEARCHTEXTAREA + ' input').attr('placeholder', s);
}.bind(this));
};
/**
* Sets filter for search input.
*
* @private
* @param {String} text The filter text
*/
Search.prototype._setFilter = function(text) {
this.messageArea.find(this.messageArea.SELECTORS.SEARCHBOX).val('');
this.messageArea.find(this.messageArea.SELECTORS.CONTACTSAREA).addClass('searchfilter');
this.messageArea.find(this.messageArea.SELECTORS.SEARCHFILTERAREA).show();
this.messageArea.find(this.messageArea.SELECTORS.SEARCHFILTER).html(text);
};
/**
* Hides filter for search input.
*
* @private
*/
Search.prototype._clearFilters = function() {
this.messageArea.find(this.messageArea.SELECTORS.CONTACTSAREA).removeClass('searchfilter');
this.messageArea.find(this.messageArea.SELECTORS.SEARCHFILTER).empty();
this.messageArea.find(this.messageArea.SELECTORS.SEARCHFILTERAREA).hide();
};
/**
* Handles clearing the search area.
*
* @private
*/
Search.prototype._clearSearchArea = function() {
this.messageArea.find(this.messageArea.SELECTORS.SEARCHRESULTSAREA).empty();
};
/**
* Handles hiding the search area.
*
* @private
*/
Search.prototype._hideSearchResults = function() {
this._clearFilters();
this.messageArea.find(this.messageArea.SELECTORS.SEARCHTEXTAREA + ' input').val('');
this._clearSearchArea();
this.messageArea.find(this.messageArea.SELECTORS.SEARCHRESULTSAREA).hide();
};
return Search;
});
+178
View File
@@ -36,6 +36,184 @@ defined('MOODLE_INTERNAL') || die();
*/
class api {
/**
* Handles searching for messages in the message area.
*
* @param int $userid The user id doing the searching
* @param string $search The string the user is searching
* @param int $limitfrom
* @param int $limitnum
* @return \core_message\output\messagearea\message_search_results
*/
public static function search_messages($userid, $search, $limitfrom = 0, $limitnum = 0) {
global $DB;
// Get the user fields we want.
$ufields = \user_picture::fields('u', array('lastaccess'), 'userfrom_id', 'userfrom_');
$ufields2 = \user_picture::fields('u2', array('lastaccess'), 'userto_id', 'userto_');
// Get all the messages for the user.
$sql = "SELECT m.id, m.useridfrom, m.useridto, m.subject, m.fullmessage, m.fullmessagehtml, m.fullmessageformat,
m.smallmessage, m.notification, m.timecreated, 0 as isread, $ufields, $ufields2
FROM {message} m
JOIN {user} u
ON m.useridfrom = u.id
JOIN {user} u2
ON m.useridto = u2.id
WHERE ((useridto = ? AND timeusertodeleted = 0)
OR (useridfrom = ? AND timeuserfromdeleted = 0))
AND notification = 0
AND u.deleted = 0
AND u2.deleted = 0
AND " . $DB->sql_like('smallmessage', '?', false) . "
UNION ALL
SELECT mr.id, mr.useridfrom, mr.useridto, mr.subject, mr.fullmessage, mr.fullmessagehtml, mr.fullmessageformat,
mr.smallmessage, mr.notification, mr.timecreated, 1 as isread, $ufields, $ufields2
FROM {message_read} mr
JOIN {user} u
ON mr.useridfrom = u.id
JOIN {user} u2
ON mr.useridto = u2.id
WHERE ((useridto = ? AND timeusertodeleted = 0)
OR (useridfrom = ? AND timeuserfromdeleted = 0))
AND notification = 0
AND u.deleted = 0
AND u2.deleted = 0
AND " . $DB->sql_like('smallmessage', '?', false) . "
ORDER BY timecreated DESC";
$params = array($userid, $userid, '%' . $search . '%',
$userid, $userid, '%' . $search . '%');
// Convert the messages into searchable contacts with their last message being the message that was searched.
$contacts = array();
if ($messages = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum)) {
foreach ($messages as $message) {
$prefix = 'userfrom_';
if ($userid == $message->useridfrom) {
$prefix = 'userto_';
// If it from the user, then mark it as read, even if it wasn't by the receiver.
$message->isread = true;
}
$message->messageid = $message->id;
$contacts[] = \core_message\helper::create_contact($message, $prefix);
}
}
return new \core_message\output\messagearea\message_search_results($userid, $contacts);
}
/**
* Handles searching for people in a particular course in the message area.
*
* @param int $userid The user id doing the searching
* @param int $courseid The id of the course we are searching in
* @param string $search The string the user is searching
* @param int $limitfrom
* @param int $limitnum
* @return \core_message\output\messagearea\people_search_results
*/
public static function search_people_in_course($userid, $courseid, $search, $limitfrom = 0, $limitnum = 0) {
global $DB;
// Get all the users in the course.
list($esql, $params) = get_enrolled_sql(\context_course::instance($courseid), '', 0, true);
$sql = "SELECT u.*
FROM {user} u
JOIN ($esql) je ON je.id = u.id
WHERE u.deleted = 0";
// Add more conditions.
$fullname = $DB->sql_fullname();
$sql .= " AND u.id != :userid
AND " . $DB->sql_like($fullname, ':search', false) . "
ORDER BY " . $DB->sql_fullname();
$params = array_merge(array('userid' => $userid, 'search' => '%' . $search . '%'), $params);
// Convert all the user records into contacts.
$contacts = array();
if ($users = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum)) {
foreach ($users as $user) {
$contacts[] = \core_message\helper::create_contact($user);
}
}
return new \core_message\output\messagearea\people_search_results($contacts);
}
/**
* Handles searching for people in the message area.
*
* @param int $userid The user id doing the searching
* @param string $search The string the user is searching
* @param int $limitnum
* @return \core_message\output\messagearea\people_search_results
*/
public static function search_people($userid, $search, $limitnum = 0) {
global $CFG, $DB;
require_once($CFG->dirroot . '/lib/coursecatlib.php');
// Used to search for contacts.
$fullname = $DB->sql_fullname();
$ufields = \user_picture::fields('u', array('lastaccess'));
// Users not to include.
$excludeusers = array($userid, $CFG->siteguest);
list($exclude, $excludeparams) = $DB->get_in_or_equal($excludeusers, SQL_PARAMS_NAMED, 'param', false);
// Ok, let's search for contacts first.
$contacts = array();
$sql = "SELECT $ufields
FROM {user} u
JOIN {message_contacts} mc
ON u.id = mc.contactid
WHERE mc.userid = :userid
AND u.deleted = 0
AND u.confirmed = 1
AND " . $DB->sql_like($fullname, ':search', false) . "
AND u.id $exclude
ORDER BY " . $DB->sql_fullname();
if ($users = $DB->get_records_sql($sql, array('userid' => $userid, 'search' => '%' . $search . '%') +
$excludeparams, 0, $limitnum)) {
foreach ($users as $user) {
$contacts[] = \core_message\helper::create_contact($user);
}
}
// Now, let's get the courses.
$courses = array();
if ($arrcourses = \coursecat::search_courses(array('search' => $search), array('limit' => $limitnum))) {
foreach ($arrcourses as $course) {
$data = new \stdClass();
$data->id = $course->id;
$data->shortname = $course->shortname;
$data->fullname = $course->fullname;
$courses[] = $data;
}
}
// Let's get those non-contacts. Toast them gears boi.
$noncontacts = array();
$sql = "SELECT $ufields
FROM {user} u
WHERE u.deleted = 0
AND u.confirmed = 1
AND " . $DB->sql_like($fullname, ':search', false) . "
AND u.id $exclude
AND u.id NOT IN (SELECT contactid
FROM {message_contacts}
WHERE userid = :userid)
ORDER BY " . $DB->sql_fullname();
if ($users = $DB->get_records_sql($sql, array('userid' => $userid, 'search' => '%' . $search . '%') +
$excludeparams, 0, $limitnum)) {
foreach ($users as $user) {
$noncontacts[] = \core_message\helper::create_contact($user);
}
}
return new \core_message\output\messagearea\people_search_results($contacts, $courses, $noncontacts);
}
/**
* Returns the contacts and their conversation to display in the contacts area.
*
+8 -4
View File
@@ -122,9 +122,10 @@ class helper {
* Helper function for creating a contact renderable.
*
* @param \stdClass $contact
* @param string $prefix
* @return \core_message\output\messagearea\contact
*/
public static function create_contact($contact) {
public static function create_contact($contact, $prefix = '') {
global $CFG, $PAGE;
// Variables to check if we consider this user online or not.
@@ -135,7 +136,7 @@ class helper {
$time = time() - $timetoshowusers;
// Create the data we are going to pass to the renderable.
$userfields = \user_picture::unalias($contact, array('lastaccess'));
$userfields = \user_picture::unalias($contact, array('lastaccess'), $prefix . 'id', $prefix);
$data = new \stdClass();
$data->userid = $userfields->id;
$data->fullname = fullname($userfields);
@@ -146,10 +147,13 @@ class helper {
$userpicture->size = 0; // Size f2.
$data->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
// Store the message if we have it.
$data->lastmessage = null;
$data->messageid = null;
if (isset($contact->smallmessage)) {
$data->lastmessage = $contact->smallmessage;
} else {
$data->lastmessage = null;
if (isset($contact->messageid)) {
$data->messageid = $contact->messageid;
}
}
// Check if the user is online.
$data->isonline = $userfields->lastaccess >= $time;
@@ -61,6 +61,7 @@ class contact implements templatable, renderable {
$contact->fullname = $this->contact->fullname;
$contact->profileimageurl = $this->contact->profileimageurl;
$contact->profileimageurlsmall = $this->contact->profileimageurlsmall;
$contact->messageid = $this->contact->messageid;
if ($this->contact->lastmessage) {
$contact->lastmessage = shorten_text($this->contact->lastmessage, self::MAX_MSG_LENGTH);
} else {
@@ -0,0 +1,70 @@
<?php
// 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/>.
/**
* Contains class used to display message search results.
*
* @package core_message
* @copyright 2016 Mark Nelson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_message\output\messagearea;
use renderable;
use templatable;
/**
* Class used to display message search results.
*
* @package core_message
* @copyright 2016 Mark Nelson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class message_search_results implements templatable, renderable {
/**
* The id of the user that the contacts belong to.
*/
protected $userid;
/**
* The list of contacts with the.
*/
protected $contacts;
/**
* Constructor.
*
* @param int $userid The id of the user the search results belong to
* @param \core_message\output\messagearea\contact[] $contacts
*/
public function __construct($userid, $contacts) {
$this->userid = $userid;
$this->contacts = $contacts;
}
public function export_for_template(\renderer_base $output) {
$data = new \stdClass();
$data->userid = $this->userid;
$data->contacts = array();
foreach ($this->contacts as $contact) {
$data->contacts[] = $contact->export_for_template($output);
}
return $data;
}
}
@@ -0,0 +1,101 @@
<?php
// 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/>.
/**
* Contains class used to display people search results.
*
* @package core_message
* @copyright 2016 Mark Nelson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_message\output\messagearea;
use renderable;
use templatable;
/**
* Class used to display people search results.
*
* @package core_message
* @copyright 2016 Mark Nelson <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class people_search_results implements templatable, renderable {
/**
* The list of contacts
*/
protected $contacts;
/**
* The list of courses.
*/
protected $courses;
/**
* The list of non-contacts.
*/
protected $noncontacts;
/**
* Constructor.
*
* @param \core_message\output\messagearea\contact[] $contacts
* @param array $courses
* @param \core_message\output\messagearea\contact[] $noncontacts
*/
public function __construct($contacts, $courses = array(), $noncontacts = array()) {
$this->contacts = $contacts;
$this->courses = $courses;
$this->noncontacts = $noncontacts;
}
public function export_for_template(\renderer_base $output) {
// Set the defaults for the data we are going to export.
$data = new \stdClass();
$data->hascontacts = false;
$data->contacts = array();
$data->hascourses = false;
$data->courses = array();
$data->hasnoncontacts = false;
$data->noncontacts = array();
// Check if there are any contacts.
if (!empty($this->contacts)) {
$data->hascontacts = true;
foreach ($this->contacts as $contact) {
$data->contacts[] = $contact->export_for_template($output);
}
}
// Check if there are any courses.
if (!empty($this->courses)) {
$data->hascourses = true;
$data->courses = $this->courses;
}
// Check if there are any non-contacts.
if (!empty($this->noncontacts)) {
$data->hasnoncontacts = true;
foreach ($this->noncontacts as $noncontact) {
$data->noncontacts[] = $noncontact->export_for_template($output);
}
}
return $data;
}
}
+263
View File
@@ -420,6 +420,269 @@ class core_message_external extends external_api {
return null;
}
/**
* Get messagearea search people parameters.
*
* @return external_function_parameters
* @since 3.2
*/
public static function data_for_messagearea_search_people_in_course_parameters() {
return new external_function_parameters(
array(
'userid' => new external_value(PARAM_INT, 'The id of the user who is performing the search'),
'courseid' => new external_value(PARAM_INT, 'The id of the course'),
'search' => new external_value(PARAM_RAW, 'The string being searched'),
'limitfrom' => new external_value(PARAM_INT, 'Limit from', VALUE_DEFAULT, 0),
'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 0)
)
);
}
/**
* Get messagearea search people results.
*
* @param int $userid The id of the user who is performing the search
* @param int $courseid The id of the course
* @param string $search The string being searched
* @param int $limitfrom
* @param int $limitnum
* @return stdClass
* @throws moodle_exception
* @since 3.2
*/
public static function data_for_messagearea_search_people_in_course($userid, $courseid, $search, $limitfrom = 0, $limitnum = 0) {
global $CFG, $PAGE;
// Check if messaging is enabled.
if (!$CFG->messaging) {
throw new moodle_exception('disabled', 'message');
}
$params = array(
'userid' => $userid,
'courseid' => $courseid,
'search' => $search,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum
);
self::validate_parameters(self::data_for_messagearea_search_people_in_course_parameters(), $params);
self::validate_context(context_user::instance($userid));
$search = \core_message\api::search_people_in_course($userid, $courseid, $search, $limitfrom, $limitnum);
$renderer = $PAGE->get_renderer('core_message');
return $search->export_for_template($renderer);
}
/**
* Get messagearea search people returns.
*
* @return external_single_structure
* @since 3.2
*/
public static function data_for_messagearea_search_people_in_course_returns() {
return new external_single_structure(
array(
'hascontacts' => new external_value(PARAM_BOOL, 'Are there contacts?'),
'contacts' => new external_multiple_structure(
new external_single_structure(
array(
'userid' => new external_value(PARAM_INT, 'The user\'s id'),
'fullname' => new external_value(PARAM_NOTAGS, 'The user\'s name'),
'profileimageurl' => new external_value(PARAM_URL, 'User picture URL'),
'profileimageurlsmall' => new external_value(PARAM_URL, 'Small user picture URL'),
'lastmessage' => new external_value(PARAM_NOTAGS, 'The user\'s last message'),
'isonline' => new external_value(PARAM_BOOL, 'The user\'s online status'),
'isread' => new external_value(PARAM_BOOL, 'If the user has read the message'),
)
)
),
)
);
}
/**
* Get messagearea search people parameters.
*
* @return external_function_parameters
* @since 3.2
*/
public static function data_for_messagearea_search_people_parameters() {
return new external_function_parameters(
array(
'userid' => new external_value(PARAM_INT, 'The id of the user who is performing the search'),
'search' => new external_value(PARAM_RAW, 'The string being searched'),
'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 0)
)
);
}
/**
* Get messagearea search people results.
*
* @param int $userid The id of the user who is performing the search
* @param string $search The string being searched
* @param int $limitnum
* @return stdClass
* @throws moodle_exception
* @since 3.2
*/
public static function data_for_messagearea_search_people($userid, $search, $limitnum = 0) {
global $CFG, $PAGE;
// Check if messaging is enabled.
if (!$CFG->messaging) {
throw new moodle_exception('disabled', 'message');
}
$params = array(
'userid' => $userid,
'search' => $search,
'limitnum' => $limitnum
);
self::validate_parameters(self::data_for_messagearea_search_people_parameters(), $params);
self::validate_context(context_user::instance($userid));
$search = \core_message\api::search_people($userid, $search, $limitnum);
$renderer = $PAGE->get_renderer('core_message');
return $search->export_for_template($renderer);
}
/**
* Get messagearea search people returns.
*
* @return external_single_structure
* @since 3.2
*/
public static function data_for_messagearea_search_people_returns() {
return new external_single_structure(
array(
'hascontacts' => new external_value(PARAM_BOOL, 'Are there contacts?'),
'contacts' => new external_multiple_structure(
new external_single_structure(
array(
'userid' => new external_value(PARAM_INT, 'The user\'s id'),
'fullname' => new external_value(PARAM_NOTAGS, 'The user\'s name'),
'profileimageurl' => new external_value(PARAM_URL, 'User picture URL'),
'profileimageurlsmall' => new external_value(PARAM_URL, 'Small user picture URL'),
'lastmessage' => new external_value(PARAM_NOTAGS, 'The user\'s last message'),
'isonline' => new external_value(PARAM_BOOL, 'The user\'s online status'),
'isread' => new external_value(PARAM_BOOL, 'If the user has read the message'),
)
)
),
'hascourses' => new external_value(PARAM_BOOL, 'Are there courses?'),
'courses' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'The course id'),
'shortname' => new external_value(PARAM_NOTAGS, 'The course shortname'),
'fullname' => new external_value(PARAM_NOTAGS, 'The course fullname'),
)
)
),
'hasnoncontacts' => new external_value(PARAM_BOOL, 'Are there non-contacts?'),
'noncontacts' => new external_multiple_structure(
new external_single_structure(
array(
'userid' => new external_value(PARAM_INT, 'The user\'s id'),
'fullname' => new external_value(PARAM_NOTAGS, 'The user\'s name'),
'profileimageurl' => new external_value(PARAM_URL, 'User picture URL'),
'profileimageurlsmall' => new external_value(PARAM_URL, 'Small user picture URL'),
'lastmessage' => new external_value(PARAM_NOTAGS, 'The user\'s last message'),
'isonline' => new external_value(PARAM_BOOL, 'The user\'s online status'),
'isread' => new external_value(PARAM_BOOL, 'If the user has read the message'),
)
)
)
)
);
}
/**
* Get messagearea search messages parameters.
*
* @return external_function_parameters
* @since 3.2
*/
public static function data_for_messagearea_search_messages_parameters() {
return new external_function_parameters(
array(
'userid' => new external_value(PARAM_INT, 'The id of the user who is performing the search'),
'search' => new external_value(PARAM_RAW, 'The string being searched'),
'limitfrom' => new external_value(PARAM_INT, 'Limit from', VALUE_DEFAULT, 0),
'limitnum' => new external_value(PARAM_INT, 'Limit number', VALUE_DEFAULT, 0)
)
);
}
/**
* Get messagearea search messages results.
*
* @param int $userid The id of the user who is performing the search
* @param string $search The string being searched
* @param int $limitfrom
* @param int $limitnum
* @return stdClass
* @throws moodle_exception
* @since 3.2
*/
public static function data_for_messagearea_search_messages($userid, $search, $limitfrom = 0, $limitnum = 0) {
global $CFG, $PAGE;
// Check if messaging is enabled.
if (!$CFG->messaging) {
throw new moodle_exception('disabled', 'message');
}
$params = array(
'userid' => $userid,
'search' => $search,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum
);
self::validate_parameters(self::data_for_messagearea_search_messages_parameters(), $params);
self::validate_context(context_user::instance($userid));
$search = \core_message\api::search_messages($userid, $search, $limitfrom, $limitnum);
$renderer = $PAGE->get_renderer('core_message');
return $search->export_for_template($renderer);
}
/**
* Get messagearea search messages returns.
*
* @return external_single_structure
* @since 3.2
*/
public static function data_for_messagearea_search_messages_returns() {
return new external_single_structure(
array(
'userid' => new external_value(PARAM_INT, 'The id of the user who we are viewing conversations for'),
'contacts' => new external_multiple_structure(
new external_single_structure(
array(
'userid' => new external_value(PARAM_INT, 'The user\'s id'),
'fullname' => new external_value(PARAM_NOTAGS, 'The user\'s name'),
'profileimageurl' => new external_value(PARAM_URL, 'User picture URL'),
'profileimageurlsmall' => new external_value(PARAM_URL, 'Small user picture URL'),
'lastmessage' => new external_value(PARAM_NOTAGS, 'The user\'s last message'),
'messageid' => new external_value(PARAM_INT, 'The unique search message id'),
'isonline' => new external_value(PARAM_BOOL, 'The user\'s online status'),
'isread' => new external_value(PARAM_BOOL, 'If the user has read the message'),
)
)
)
)
);
}
/**
* The messagearea conversations parameters.
*
@@ -1,4 +1,6 @@
<div class="row-fluid contact {{#selected}}selected{{/selected}}" data-action="{{#lastmessage}}view-contact-msg{{/lastmessage}}{{^lastmessage}}view-contact-profile{{/lastmessage}}" data-userid="{{userid}}" data-region="contact" tabindex="0">
<div class="row-fluid contact {{#selected}}selected{{/selected}}"
data-action="{{#lastmessage}}view-contact-msg{{/lastmessage}}{{^lastmessage}}view-contact-profile{{/lastmessage}}"
data-userid="{{userid}}" data-messageid="{{#messageid}}{{.}}{{isread}}{{/messageid}}" data-region="contact" tabindex="0">
<div class="span3 picture">
<img src="{{profileimageurl}}" alt="" />
</div>
@@ -1,14 +1,19 @@
<div class="searcharea" data-region="search-area">
<div class="searchtextarea" data-region="search-text-area">
<form>
<label class="accesshide" for="filter{{uniqid}}">{{#str}}search{{/str}}</label>
<input data-region="search-box" type="text" id="filter{{uniqid}}" placeholder="{{#str}}search{{/str}}" value="{{search}}">
<label class="accesshide" for="searchtext">{{#str}}search{{/str}}</label>
<input data-region="search-box" type="text" id="searchtext" placeholder="{{#str}}searchmessages, message{{/str}}">
</form>
<div data-region="search-filter-area" class="searchfilterarea" style="display:none">
<div data-region="search-filter" class="searchfilter"></div>
<div data-action="search-filter-delete" class="searchfilterdelete">{{#pix}}t/delete{{/pix}}</div>
</div>
</div>
<div class="contacts" data-region="contacts" data-region-content="conversations" role="tabpanel" id="conversations-tab-panel">
{{> core_message/message_area_contacts }}
</div>
{{! Hidden div to load the other tabs items in via JS when appropriate. }}
{{! Hidden divs to load the other tab and search panels via JS when appropriate. }}
<div class="contacts" data-region="contacts" data-region-content="contacts" style="display:none;" role="tabpanel" id="contacts-tab-panel"></div>
<div class="contacts searcharea" data-region="search-results-area" style="display:none;"></div>
<div class="row-fluid tabs">
<div class="span6 tab tabconversations selected" data-action="conversations-view" role="tab" aria-controls="conversations-tab-panel" aria-selected="true" tabindex="0">
<div class="tabimage">{{#pix}}t/message, moodle{{/pix}}</div>
@@ -0,0 +1,19 @@
{{#hascontacts}}
{{#contacts}}
{{> core_message/message_area_contact }}
{{/contacts}}
{{/hascontacts}}
{{#hascourses}}
<div class="heading">{{#str}}courses{{/str}}</div>
{{#courses}}
<div class="course" data-action="search-people-in-course" data-courseid="{{id}}">
{{fullname}}
</div>
{{/courses}}
{{/hascourses}}
{{#hasnoncontacts}}
<div class="heading">{{#str}}noncontacts, message{{/str}}</div>
{{#noncontacts}}
{{> core_message/message_area_contact }}
{{/noncontacts}}
{{/hasnoncontacts}}
+48 -1
View File
@@ -51,7 +51,18 @@
border-right: 1px solid #e3e3e3;
height: 600px;
.searcharea {
&.searchfilter {
.searchtextarea {
height: 80px;
}
.searcharea {
height: 470px;
}
}
.searchtextarea {
padding: 5px;
text-align: center;
height: 50px;
@@ -85,6 +96,42 @@
transition: background-color linear 0.2s;
}
}
.searchfilterarea {
line-height: 20px;
.searchfilter {
float: left;
}
.searchfilterdelete {
float: left;
margin-left: 5px;
cursor: pointer;
}
}
}
.searcharea {
.heading {
text-align: center;
border-top: 1px solid black;
border-bottom: 1px solid black;
font-size: 14px;
font-weight: bold;
}
.course {
text-align: center;
&:hover {
background-color: #4f94cd;
color: #fff;
border: none;
cursor: pointer;
}
}
}
.contacts {
+38 -5
View File
@@ -5795,7 +5795,13 @@ a.ygtvspacer:hover {
border-right: 1px solid #e3e3e3;
height: 600px;
}
.messaging-area-container .messaging-area .contacts-area .searcharea {
.messaging-area-container .messaging-area .contacts-area.searchfilter .searchtextarea {
height: 80px;
}
.messaging-area-container .messaging-area .contacts-area.searchfilter .searcharea {
height: 470px;
}
.messaging-area-container .messaging-area .contacts-area .searchtextarea {
padding: 5px;
text-align: center;
height: 50px;
@@ -5804,7 +5810,7 @@ a.ygtvspacer:hover {
background-color: #fff;
transition: background-color linear 0.2s;
}
.messaging-area-container .messaging-area .contacts-area .searcharea input {
.messaging-area-container .messaging-area .contacts-area .searchtextarea input {
background-color: #f5f5f5;
border: 0;
width: 90%;
@@ -5813,19 +5819,46 @@ a.ygtvspacer:hover {
box-shadow: none;
transition: background-color linear 0.2s;
}
.messaging-area-container .messaging-area .contacts-area .searcharea input:focus {
.messaging-area-container .messaging-area .contacts-area .searchtextarea input:focus {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.messaging-area-container .messaging-area .contacts-area .searcharea.searching {
.messaging-area-container .messaging-area .contacts-area .searchtextarea.searching {
background-color: #f5f5f5;
transition: background-color linear 0.2s;
}
.messaging-area-container .messaging-area .contacts-area .searcharea.searching input {
.messaging-area-container .messaging-area .contacts-area .searchtextarea.searching input {
background-color: #fff;
transition: background-color linear 0.2s;
}
.messaging-area-container .messaging-area .contacts-area .searchtextarea .searchfilterarea {
line-height: 20px;
}
.messaging-area-container .messaging-area .contacts-area .searchtextarea .searchfilterarea .searchfilter {
float: left;
}
.messaging-area-container .messaging-area .contacts-area .searchtextarea .searchfilterarea .searchfilterdelete {
float: left;
margin-left: 5px;
cursor: pointer;
}
.messaging-area-container .messaging-area .contacts-area .searcharea .heading {
text-align: center;
border-top: 1px solid black;
border-bottom: 1px solid black;
font-size: 14px;
font-weight: bold;
}
.messaging-area-container .messaging-area .contacts-area .searcharea .course {
text-align: center;
}
.messaging-area-container .messaging-area .contacts-area .searcharea .course:hover {
background-color: #4f94cd;
color: #fff;
border: none;
cursor: pointer;
}
.messaging-area-container .messaging-area .contacts-area .contacts {
height: 500px;
overflow-y: scroll;