Merge pull request #5703 from RocketChat/toolbar-search

add toolbar search
pull/5756/merge
Gabriel Engel 9 years ago committed by GitHub
commit ecb3de6734
  1. 160
      packages/rocketchat-theme/client/imports/base.less
  2. 27
      packages/rocketchat-theme/client/imports/rtl.less
  3. 27
      packages/rocketchat-theme/server/colors.less
  4. 2
      packages/rocketchat-theme/server/lesshat.less
  5. 5
      packages/rocketchat-ui-master/master/main.coffee
  6. 1
      packages/rocketchat-ui-master/master/main.html
  7. 54
      packages/rocketchat-ui-message/message/popup/messagePopup.coffee
  8. 10
      packages/rocketchat-ui-message/message/popup/messagePopup.html
  9. 2
      packages/rocketchat-ui-sidenav/package.js
  10. 1
      packages/rocketchat-ui-sidenav/side-nav/sideNav.html
  11. 23
      packages/rocketchat-ui-sidenav/side-nav/toolbar.html
  12. 149
      packages/rocketchat-ui-sidenav/side-nav/toolbar.js
  13. 5
      packages/rocketchat-ui/package.js
  14. 59
      packages/rocketchat-ui/views/app/spotlight/mobileMessageMenu.coffee
  15. 64
      packages/rocketchat-ui/views/app/spotlight/spotlight.coffee
  16. 9
      packages/rocketchat-ui/views/app/spotlight/spotlight.html
  17. 7
      packages/rocketchat-ui/views/app/spotlight/spotlightTemplate.html
  18. 13
      packages/rocketchat-ui/views/app/spotlight/spotlightTemplate.js
  19. 2
      server/publications/spotlight.coffee

@ -1110,7 +1110,7 @@ label.required::after {
top: 0;
left: 0;
height: 100%;
z-index: 2;
z-index: 3;
overflow-y: auto;
overflow-x: hidden;
width: @rooms-box-width;
@ -1336,6 +1336,8 @@ label.required::after {
direction: ltr;
padding-left: 8px;
padding-bottom: 1em;
position: relative;
padding-top: 50px;
}
}
@ -1370,7 +1372,7 @@ label.required::after {
top: 0;
left: 0;
width: 100%;
z-index: 1;
z-index: 2;
cursor: pointer;
min-height: @header-min-height;
height: @header-min-height;
@ -1380,7 +1382,7 @@ label.required::after {
position: absolute;
top: 18px;
right: 8px;
z-index: 2;
z-index: 3;
cursor: pointer;
}
@ -1393,7 +1395,7 @@ label.required::after {
text-align: right;
min-height: @footer-min-height;
height: @footer-min-height;
z-index: 1;
z-index: 2;
.logo {
display: block;
@ -1585,7 +1587,7 @@ label.required::after {
-webkit-justify-content: center;
&.top-unread-rooms {
top: 60px;
top: 105px;
}
&.bottom-unread-rooms {
@ -1611,6 +1613,43 @@ label.required::after {
}
}
.toolbar {
position: fixed;
width: 244px;
z-index: 3;
top: 60px;
padding-top: 10px;
}
.toolbar-search {
padding-right: 8px;
position: relative;
}
.toolbar-search__icon {
position: absolute;
top: 0;
left: 0;
line-height: 35px;
width: 35px;
text-align: center;
}
.toolbar-search__icon--cancel {
left: auto;
right: 8px;
opacity: 0;
transition: opacity 0.3s;
}
.toolbar-search__input {
padding: 6px 35px !important;
&:focus + .toolbar-search__icon--cancel {
opacity: 1;
}
}
.new-room-highlight a {
-webkit-animation: highlight 2s infinite;
-moz-animation: highlight 2s infinite;
@ -1700,90 +1739,6 @@ label.required::after {
}
}
.spotlight {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
display: flex;
display: -webkit-flex;
justify-content: center;
padding: 0 40px;
> .spotlight-input {
position: relative;
width: 100%;
max-width: 600px;
font-size: 24px;
margin-top: 6%;
margin-bottom: auto;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5);
> input {
box-shadow: none;
border-width: 0;
line-height: 46px;
height: 46px;
padding: 0 10px 0 46px;
}
> i {
position: absolute;
z-index: 10;
line-height: 46px;
width: 46px;
text-align: center;
font-weight: 100;
}
.message-popup {
position: relative;
box-shadow: none;
border-radius: 0;
.popup-item {
border-top: 1px solid #eaeaea;
line-height: 40px;
font-size: 20px;
padding: 0 10px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
i {
margin-right: 4px;
line-height: 28px;
display: inline-block;
border-radius: 20px;
width: 28px;
}
&.selected {
i {
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
}
}
span {
float: right;
border-radius: 2px;
margin-top: 10px;
min-width: 20px;
padding: 0 2px;
text-align: center;
font-size: 14px;
line-height: 20px;
font-weight: 800;
}
}
}
}
}
// MAIN CONTENT + MAIN PAGES //
.main-content {
@ -2506,11 +2461,30 @@ label.required::after {
0 -1px 10px 0 rgba(0, 0, 0, 0.2),
0 1px 1px rgba(0, 0, 0, 0.16);
border-radius: 5px;
}
.message-popup.popup-down {
bottom: auto;
top: 0;
&.popup-down {
bottom: auto;
top: 0;
}
&.search-results-list {
top: 25px;
height: calc(~"100vh - 200px");
overflow-y: auto;
box-shadow: none;
border-radius: 0;
.popup-item {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 6px;
}
.unread {
top: 8px;
}
}
}
.message-popup-title {

@ -802,19 +802,22 @@
.left(12px);
}
.spotlight {
> .spotlight-input {
.icon-search {
left: 0;
}
.toolbar-search {
padding: 0 0 0 8px;
}
.message-popup {
.popup-item {
span {
float: left;
}
}
}
.toolbar-search__icon {
right: 0;
}
.toolbar-search__icon--cancel {
right: auto;
left: 0;
}
.message-popup.search-results-list {
.popup-item {
padding-right: 6px;
}
}
}

@ -179,6 +179,10 @@
background-color: @transparent-light;
}
.border-transparent-lighter {
border-color: @transparent-lighter;
}
.background-transparent-lightest {
background-color: @transparent-lightest;
}
@ -241,8 +245,18 @@
.input-shade(@primary-font-color, @content-background-color);
.toolbar-search__input {
&:focus {
border-color: fade(@primary-background-contrast, 50%);
}
&::placeholder {
color: @transparent-lighter;
}
}
.flex-nav {
.input-shade(@primary-background-contrast, @primary-background-color);
.input-shade(@primary-background-contrast, @transparent-lighter);
input {
&:focus {
@ -529,7 +543,8 @@ a:hover {
background-color: @tertiary-font-color;
}
.rooms-list {
.rooms-list,
.toolbar {
background-color: lighten(@primary-background-color, 2.5%);
}
@ -567,6 +582,14 @@ a:hover {
}
}
.message-popup.search-results-list {
background-color: lighten(@primary-background-color, 2.5%);
.popup-item.selected {
background-color: @transparent-darker;
}
}
/** ----------------------------------------------------------------------------
* Flex tabs / admin fly-out panels
*/

@ -34,7 +34,7 @@
}
}
.diabled label,
.disabled label,
[disabled] label {
color: mix(@color, @bg, 75%);
}

@ -7,10 +7,7 @@ Template.body.onRendered ->
if e.keyCode is 80 and (e.ctrlKey is true or e.metaKey is true) and e.shiftKey is false
e.preventDefault()
e.stopPropagation()
spotlight.show()
if e.keyCode is 27
spotlight.hide()
toolbarSearch.focus()
unread = Session.get('unread')
if e.keyCode is 27 and e.shiftKey is true and unread? and unread isnt ''

@ -42,7 +42,6 @@
{{#if requirePasswordChange}}
{{> loginLayout center="resetPassword"}}
{{else}}
{{> spotlight}}
{{> videoCall overlay=true}}
<div id="user-card-popover"></div>
<div id="rocket-chat" class="{{embeddedVersion}} menu-nav">

@ -51,6 +51,10 @@ Template.messagePopup.onCreated ->
template.triggerAnywhere = val(template.data.triggerAnywhere, true)
template.closeOnEsc = val(template.data.closeOnEsc, true)
template.blurOnSelectItem = val(template.data.blurOnSelectItem, false)
template.prefix = val(template.data.prefix, template.trigger)
template.suffix = val(template.data.suffix, '')
@ -96,29 +100,38 @@ Template.messagePopup.onCreated ->
if template.open.curValue isnt true or template.hasData.curValue isnt true
return
if event.which in [keys.ARROW_UP, keys.ARROW_DOWN]
event.preventDefault()
event.stopPropagation()
if event.which in [keys.ENTER, keys.TAB]
template.open.set false
if template.blurOnSelectItem is true
template.input.blur()
else
template.open.set false
template.enterValue()
event.preventDefault()
event.stopPropagation()
return
if event.which is keys.ARROW_UP
template.up()
else if event.which is keys.ARROW_DOWN
event.preventDefault()
event.stopPropagation()
return
if event.which is keys.ARROW_DOWN
template.down()
event.preventDefault()
event.stopPropagation()
return
template.setTextFilter = _.debounce (value) ->
template.textFilter.set(value)
, template.textFilterDelay
template.onInputKeyup = (event) =>
if template.open.curValue is true and event.which is keys.ESC
if template.closeOnEsc is true and template.open.curValue is true and event.which is keys.ESC
template.open.set false
event.preventDefault()
event.stopPropagation()
@ -140,6 +153,27 @@ Template.messagePopup.onCreated ->
Meteor.defer =>
template.verifySelection()
template.onFocus = (event) =>
if template.open.curValue is true
return
value = template.input.value
value = value.substr 0, getCursorPosition(template.input)
if template.matchSelectorRegex.test value
template.setTextFilter value.match(template.selectorRegex)[1]
template.open.set true
Meteor.defer =>
template.verifySelection()
else
template.open.set false
template.onBlur = (event) =>
if template.open.curValue is false
return
template.open.set false
template.enterValue = ->
if not template.value.curValue? then return
@ -188,11 +222,15 @@ Template.messagePopup.onRendered ->
$(this.input).on 'keyup', this.onInputKeyup.bind this
$(this.input).on 'keydown', this.onInputKeydown.bind this
$(this.input).on 'focus', this.onFocus.bind this
$(this.input).on 'blur', this.onBlur.bind this
Template.messagePopup.onDestroyed ->
$(this.input).off 'keyup', this.onInputKeyup
$(this.input).off 'keydown', this.onInputKeydown
$(this.input).off 'focus', this.onFocus
$(this.input).off 'blur', this.onBlur
Template.messagePopup.events
@ -220,7 +258,7 @@ Template.messagePopup.events
Template.messagePopup.helpers
isOpen: ->
Template.instance().open.get() and (Template.instance().hasData.get() or not Template.instance().parentTemplate(1).subscriptionsReady())
Template.instance().open.get() and ((Template.instance().hasData.get() or Template.instance().data.emptyTemplate?) or not Template.instance().parentTemplate(1).subscriptionsReady())
data: ->
template = Template.instance()

@ -14,6 +14,16 @@
</div>
{{/each}}
</div>
{{#unless data}}
{{#unless isLoading.get}}
{{#if emptyTemplate}}
{{> Template.dynamic template=emptyTemplate}}
{{/if}}
{{/unless}}
{{/unless}}
{{#if isLoading.get}}
{{> loading}}
{{/if}}
</div>
</div>
{{/if}}

@ -36,6 +36,7 @@ Package.onUse(function(api) {
api.addFiles('side-nav/privateGroupsFlex.html', 'client');
api.addFiles('side-nav/sideNav.html', 'client');
api.addFiles('side-nav/starredRooms.html', 'client');
api.addFiles('side-nav/toolbar.html', 'client');
api.addFiles('side-nav/unreadRooms.html', 'client');
api.addFiles('side-nav/userStatus.html', 'client');
@ -55,6 +56,7 @@ Package.onUse(function(api) {
api.addFiles('side-nav/privateGroupsFlex.coffee', 'client');
api.addFiles('side-nav/sideNav.coffee', 'client');
api.addFiles('side-nav/starredRooms.coffee', 'client');
api.addFiles('side-nav/toolbar.js', 'client');
api.addFiles('side-nav/unreadRooms.coffee', 'client');
});

@ -9,6 +9,7 @@
</div>
<div class="rooms-list" aria-label="{{_ "Channels"}}" role="region">
<div class="wrapper">
{{> toolbar}}
{{> unreadRooms }}
{{#each roomType}}

@ -0,0 +1,23 @@
<template name="toolbar">
<div class="toolbar">
<div class="toolbar-search">
<i class="toolbar-search__icon icon-search"></i>
<input type="text" name="toolbar-input" class="toolbar-search__input background-transparent-light color-content-background-color border-transparent-lighter" placeholder="{{_ "Search"}}">
<i class="toolbar-search__icon toolbar-search__icon--cancel icon-cancel"></i>
</div>
{{> messagePopup popupConfig}}
</div>
</template>
<template name="toolbarSearchList">
<i class="{{icon}} {{userStatus}}"></i>
{{name}}
{{#if unread}}
<span class="unread">{{unread}}</span>
{{/if}}
</template>
<template name="toolbarSearchListEmpty">
{{_ "Room_not_found"}}
</template>

@ -0,0 +1,149 @@
let isLoading;
let filterText = '';
let usernamesFromClient;
let resultsFromClient;
Meteor.startup(() => {
isLoading = new ReactiveVar(false);
});
const toolbarSearch = {
clear() {
$('.toolbar-search__input').val('');
$('.toolbar-search__input').trigger({
type: 'keyup',
which: 27
});
},
focus() {
$('.toolbar-search__input').focus();
}
};
this.toolbarSearch = toolbarSearch;
const getFromServer = (cb) => {
isLoading.set(true);
const currentFilter = filterText;
Meteor.call('spotlight', currentFilter, usernamesFromClient, (err, results) => {
if (currentFilter !== filterText) {
return;
}
isLoading.set(false);
if (err) {
console.log(err);
return false;
}
const resultsFromServer = [];
const usersLength = results.users.length;
const roomsLength = results.rooms.length;
if (usersLength) {
for (let i = 0; i < usersLength; i++) {
resultsFromServer.push({
_id: results.users[i]._id,
t: 'd',
name: results.users[i].username
});
}
}
if (roomsLength) {
for (let i = 0; i < roomsLength; i++) {
resultsFromServer.push({
_id: results.rooms[i]._id,
t: results.rooms[i].t,
name: results.rooms[i].name
});
}
}
if (resultsFromServer.length) {
cb(resultsFromClient.concat(resultsFromServer));
}
});
};
const getFromServerThrottled = _.throttle(getFromServer, 500);
Template.toolbar.helpers({
results() {
return Template.instance().resultsList.get();
},
popupConfig() {
const config = {
cls: 'search-results-list',
collection: RocketChat.models.Subscriptions,
template: 'toolbarSearchList',
emptyTemplate: 'toolbarSearchListEmpty',
input: '.toolbar-search__input',
closeOnEsc: false,
blurOnSelectItem: true,
isLoading: isLoading,
getFilter: function(collection, filter, cb) {
filterText = filter;
resultsFromClient = collection.find({name: new RegExp((RegExp.escape(filter)), 'i'), rid: {$ne: Session.get('openedRoom')}}, {limit: 10, sort: {unread: -1, ls: -1}}).fetch();
const resultsFromClientLength = resultsFromClient.length;
usernamesFromClient = [Meteor.user().username];
for (let i = 0; i < resultsFromClientLength; i++) {
if (resultsFromClient[i].t === 'd') {
usernamesFromClient.push(resultsFromClient[i].name);
}
}
cb(resultsFromClient);
getFromServerThrottled(cb);
},
getValue: function(_id, collection, records) {
const doc = _.findWhere(records, {_id: _id});
RocketChat.roomTypes.openRouteLink(doc.t, doc, FlowRouter.current().queryParams);
}
};
return config;
}
});
Template.toolbar.events({
'blur .toolbar-search__input'() {
toolbarSearch.clear();
},
'keyup .toolbar-search__input'(e) {
if (e.which === 27) {
e.preventDefault();
e.stopPropagation();
const $inputMessage = $('textarea.input-message');
if (0 === $inputMessage.length) {
return;
}
$inputMessage.focus();
}
}
});
Template.toolbarSearchList.helpers({
icon() {
return RocketChat.roomTypes.getIcon(this.t);
},
userStatus() {
if (this.t === 'd') {
return 'status-' + (Session.get(`user_${this.name}_status`) || 'offline');
} else {
return 'status-' + (RocketChat.roomTypes.getUserStatus(this.t, this.rid || this._id) || 'offline');
}
}
});

@ -91,8 +91,6 @@ Package.onUse(function(api) {
api.addFiles('views/app/roomSearch.html', 'client');
api.addFiles('views/app/secretURL.html', 'client');
api.addFiles('views/app/userSearch.html', 'client');
api.addFiles('views/app/spotlight/spotlight.html', 'client');
api.addFiles('views/app/spotlight/spotlightTemplate.html', 'client');
api.addFiles('views/app/videoCall/videoButtons.html', 'client');
api.addFiles('views/app/videoCall/videoCall.html', 'client');
@ -106,9 +104,6 @@ Package.onUse(function(api) {
api.addFiles('views/app/room.coffee', 'client');
api.addFiles('views/app/roomSearch.coffee', 'client');
api.addFiles('views/app/secretURL.coffee', 'client');
api.addFiles('views/app/spotlight/mobileMessageMenu.coffee', 'client');
api.addFiles('views/app/spotlight/spotlight.coffee', 'client');
api.addFiles('views/app/spotlight/spotlightTemplate.js', 'client');
api.addFiles('views/app/videoCall/videoButtons.coffee', 'client');
api.addFiles('views/app/videoCall/videoCall.coffee', 'client');
});

@ -1,59 +0,0 @@
@mobileMessageMenu =
show: (message, template, e, scope) ->
if not window.plugins?.actionsheet?
return
options =
'androidTheme': window.plugins.actionsheet.ANDROID_THEMES.THEME_HOLO_LIGHT
'buttonLabels': [
TAPi18n.__('Report Abuse')
]
androidEnableCancelButton: true
addCancelButtonWithLabel: TAPi18n.__('Cancel')
# 'position': [20, 40] // for iPad pass in the [x, y] position of the popover
buttonActions = [
mobileMessageMenu.reportAbuse
]
buttons = RocketChat.MessageAction.getButtons message, (message.customClass or 'message-mobile')
for button in buttons
if button.id is 'delete-message'
options.addDestructiveButtonWithLabel = TAPi18n.__(button.i18nLabel)
buttonActions.unshift button.action
else
buttonActions.push button.action
options.buttonLabels.push TAPi18n.__(button.i18nLabel)
window.plugins.actionsheet.show options, (buttonIndex) ->
if buttonActions[buttonIndex-1]?
buttonActions[buttonIndex-1].call scope, e, template, message
reportAbuse: (e, t, message) ->
swal {
title: TAPi18n.__('Report_this_message_question_mark')
text: message.msg
inputPlaceholder: TAPi18n.__('Why_do_you_want_to_report_question_mark')
type: 'input'
showCancelButton: true
confirmButtonColor: '#DD6B55'
confirmButtonText: TAPi18n.__("Report_exclamation_mark")
cancelButtonText: TAPi18n.__('Cancel')
closeOnConfirm: false
html: false
}, (inputValue) ->
if inputValue is false
return false
if inputValue is ""
swal.showInputError(TAPi18n.__("You_need_to_write_something"))
return false
Meteor.call 'reportMessage', message, inputValue
swal
title: TAPi18n.__("Report_sent")
text: TAPi18n.__("Thank_you_exclamation_mark")
type: 'success'
timer: 1000
showConfirmButton: false

@ -1,64 +0,0 @@
@spotlight =
hide: ->
$('.spotlight').addClass('hidden')
show: ->
$('.spotlight input').val('')
$('.spotlight').removeClass('hidden')
$('.spotlight input').focus()
getFromServer = (filter, records, cb) =>
Meteor.call 'spotlight', filter, Meteor.user().username, (err, results) ->
if err?
return console.log err
server = []
if results?.users?.length > 0
for user in results.users when not _.findWhere(records, {t: 'd', name: user.username})?
server.push({
_id: user._id
t: 'd',
name: user.username
})
if results?.rooms?.length > 0
for room in results.rooms
server.push({
_id: room._id
t: room.t,
name: room.name
})
if server.length > 0
cb(records.concat(server))
getFromServerDelayed = _.throttle getFromServer, 500
Template.spotlight.helpers
popupConfig: ->
config =
cls: 'popup-down'
collection: RocketChat.models.Subscriptions
template: 'spotlightTemplate'
input: '[name=spotlight]'
getFilter: (collection, filter, cb) ->
exp = new RegExp("#{RegExp.escape filter}", 'i')
records = collection.find({name: exp, rid: {$ne: Session.get('openedRoom')}}, {limit: 10, sort: {unread: -1, ls: -1}}).fetch()
cb(records)
if filter?.trim().length < 1 or records.length >= 5
return
getFromServerDelayed(filter, records, cb)
getValue: (_id, collection, records, firstPartValue) ->
doc = _.findWhere(records, {_id: _id})
RocketChat.roomTypes.openRouteLink(doc.t, doc, FlowRouter.current().queryParams)
spotlight.hide()
return config

@ -1,9 +0,0 @@
<template name="spotlight">
<div class="spotlight hidden background-transparent-darker">
<div class="spotlight-input secondary-background-color secondary-font-color">
<i class="icon-search secondary-font-color"></i>
<input type="text" name="spotlight">
{{> messagePopup popupConfig}}
</div>
</div>
</template>

@ -1,7 +0,0 @@
<template name="spotlightTemplate">
<i class="{{icon}} {{userStatus}}"></i>
{{name}}
{{#if unread}}
<span>{{unread}}</span>
{{/if}}
</template>

@ -1,13 +0,0 @@
Template.spotlightTemplate.helpers({
icon() {
return RocketChat.roomTypes.getIcon(this.t);
},
userStatus() {
if (this.t === 'd') {
return 'status-' + (Session.get(`user_${this.name}_status`) || 'offline');
} else {
return 'status-' + (RocketChat.roomTypes.getUserStatus(this.t, this.rid || this._id) || 'offline');
}
}
});

@ -23,4 +23,4 @@ DDPRateLimiter.addRule
name: 'spotlight'
userId: (userId) ->
return true
, 10, 10000
, 100, 100000

Loading…
Cancel
Save