2017-04-12 08:27:57 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2015-06-14 23:53:32 -08:00
// See License.txt for license information.
2016-08-18 18:37:09 -04:00
import SearchResultsHeader from './search_results_header.jsx' ;
import SearchResultsItem from './search_results_item.jsx' ;
2016-03-14 08:50:46 -04:00
import ChannelStore from 'stores/channel_store.jsx' ;
import SearchStore from 'stores/search_store.jsx' ;
import UserStore from 'stores/user_store.jsx' ;
2016-08-16 23:54:11 +05:00
import PreferenceStore from 'stores/preference_store.jsx' ;
2016-12-22 17:19:19 +00:00
import WebrtcStore from 'stores/webrtc_store.jsx' ;
2016-08-18 18:37:09 -04:00
2016-03-14 08:50:46 -04:00
import * as Utils from 'utils/utils.jsx' ;
2016-08-04 11:38:09 -04:00
import Constants from 'utils/constants.jsx' ;
2016-08-16 23:54:11 +05:00
const Preferences = Constants . Preferences ;
2015-06-14 23:53:32 -08:00
2016-08-18 18:37:09 -04:00
import $ from 'jquery' ;
2017-05-18 09:28:18 -04:00
import PropTypes from 'prop-types' ;
2016-08-18 18:37:09 -04:00
import React from 'react' ;
2016-03-14 08:50:46 -04:00
import { FormattedMessage , FormattedHTMLMessage } from 'react-intl' ;
2016-02-01 17:23:45 -03:00
2015-09-02 09:45:12 -07:00
function getStateFromStores ( ) {
2016-11-22 09:13:14 -05:00
const results = JSON . parse ( JSON . stringify ( SearchStore . getSearchResults ( ) ) ) ;
2016-02-08 13:36:13 -05:00
const channels = new Map ( ) ;
2016-02-10 09:30:35 -08:00
if ( results && results . order ) {
const channelIds = results . order . map ( ( postId ) => results . posts [ postId ] . channel _id ) ;
for ( const id of channelIds ) {
if ( channels . has ( id ) ) {
continue ;
}
channels . set ( id , ChannelStore . get ( id ) ) ;
}
2016-02-08 13:36:13 -05:00
}
return {
results ,
2016-05-16 19:09:58 -04:00
channels ,
2016-08-18 18:37:09 -04:00
searchTerm : SearchStore . getSearchTerm ( ) ,
flaggedPosts : PreferenceStore . getCategory ( Constants . Preferences . CATEGORY _FLAGGED _POST )
2016-02-08 13:36:13 -05:00
} ;
2015-09-02 09:45:12 -07:00
}
2015-07-13 04:23:24 -07:00
2015-09-02 09:45:12 -07:00
export default class SearchResults extends React . Component {
constructor ( props ) {
super ( props ) ;
2015-06-14 23:53:32 -08:00
2015-09-02 09:45:12 -07:00
this . mounted = false ;
2015-06-14 23:53:32 -08:00
2015-09-02 09:45:12 -07:00
this . onChange = this . onChange . bind ( this ) ;
2016-02-24 07:59:33 -05:00
this . onUserChange = this . onUserChange . bind ( this ) ;
2016-08-18 18:37:09 -04:00
this . onPreferenceChange = this . onPreferenceChange . bind ( this ) ;
2016-12-22 17:19:19 +00:00
this . onBusy = this . onBusy . bind ( this ) ;
2015-09-02 09:45:12 -07:00
this . resize = this . resize . bind ( this ) ;
2016-08-16 23:54:11 +05:00
this . onPreferenceChange = this . onPreferenceChange . bind ( this ) ;
2016-12-22 17:19:19 +00:00
this . onStatusChange = this . onStatusChange . bind ( this ) ;
2015-10-16 12:32:19 +03:00
this . handleResize = this . handleResize . bind ( this ) ;
2015-06-14 23:53:32 -08:00
2015-10-16 12:32:19 +03:00
const state = getStateFromStores ( ) ;
state . windowWidth = Utils . windowWidth ( ) ;
state . windowHeight = Utils . windowHeight ( ) ;
2016-02-24 07:59:33 -05:00
state . profiles = JSON . parse ( JSON . stringify ( UserStore . getProfiles ( ) ) ) ;
2016-08-16 23:54:11 +05:00
state . compactDisplay = PreferenceStore . get ( Preferences . CATEGORY _DISPLAY _SETTINGS , Preferences . MESSAGE _DISPLAY , Preferences . MESSAGE _DISPLAY _DEFAULT ) === Preferences . MESSAGE _DISPLAY _COMPACT ;
2016-12-22 17:19:19 +00:00
state . isBusy = WebrtcStore . isBusy ( ) ;
state . statuses = Object . assign ( { } , UserStore . getStatuses ( ) ) ;
2015-10-16 12:32:19 +03:00
this . state = state ;
2015-06-14 23:53:32 -08:00
}
2015-09-02 09:45:12 -07:00
componentDidMount ( ) {
this . mounted = true ;
2016-08-18 18:37:09 -04:00
2015-10-26 15:16:14 -04:00
SearchStore . addSearchChangeListener ( this . onChange ) ;
2016-02-08 13:36:13 -05:00
ChannelStore . addChangeListener ( this . onChange ) ;
2016-08-16 23:54:11 +05:00
PreferenceStore . addChangeListener ( this . onPreferenceChange ) ;
2016-02-24 07:59:33 -05:00
UserStore . addChangeListener ( this . onUserChange ) ;
2016-12-22 17:19:19 +00:00
UserStore . addStatusesChangeListener ( this . onStatusChange ) ;
2016-08-18 18:37:09 -04:00
PreferenceStore . addChangeListener ( this . onPreferenceChange ) ;
2016-12-22 17:19:19 +00:00
WebrtcStore . addBusyListener ( this . onBusy ) ;
2016-08-18 18:37:09 -04:00
2015-06-14 23:53:32 -08:00
this . resize ( ) ;
2015-10-16 12:32:19 +03:00
window . addEventListener ( 'resize' , this . handleResize ) ;
2016-03-23 04:06:08 +05:00
if ( ! Utils . isMobile ( ) ) {
$ ( '.sidebar--right .search-items-container' ) . perfectScrollbar ( ) ;
}
2015-09-02 09:45:12 -07:00
}
2016-02-08 13:36:13 -05:00
shouldComponentUpdate ( nextProps , nextState ) {
2016-12-22 17:19:19 +00:00
if ( ! Utils . areObjectsEqual ( nextState . statuses , this . state . statuses ) ) {
return true ;
}
2016-02-25 19:02:30 -05:00
if ( ! Utils . areObjectsEqual ( this . props , nextProps ) ) {
return true ;
}
if ( ! Utils . areObjectsEqual ( this . state , nextState ) ) {
return true ;
}
2016-08-16 23:54:11 +05:00
if ( nextState . compactDisplay !== this . state . compactDisplay ) {
return true ;
}
2016-12-22 17:19:19 +00:00
if ( nextState . isBusy !== this . state . isBusy ) {
return true ;
}
2016-02-25 19:02:30 -05:00
return false ;
2016-02-08 13:36:13 -05:00
}
2015-09-02 09:45:12 -07:00
componentWillUnmount ( ) {
2016-08-18 18:37:09 -04:00
this . mounted = false ;
2015-10-26 15:16:14 -04:00
SearchStore . removeSearchChangeListener ( this . onChange ) ;
2016-02-08 13:36:13 -05:00
ChannelStore . removeChangeListener ( this . onChange ) ;
2016-08-16 23:54:11 +05:00
PreferenceStore . removeChangeListener ( this . onPreferenceChange ) ;
2016-02-24 07:59:33 -05:00
UserStore . removeChangeListener ( this . onUserChange ) ;
2016-12-22 17:19:19 +00:00
UserStore . removeStatusesChangeListener ( this . onStatusChange ) ;
2016-08-18 18:37:09 -04:00
PreferenceStore . removeChangeListener ( this . onPreferenceChange ) ;
2016-12-22 17:19:19 +00:00
WebrtcStore . removeBusyListener ( this . onBusy ) ;
2016-08-18 18:37:09 -04:00
2015-10-16 12:32:19 +03:00
window . removeEventListener ( 'resize' , this . handleResize ) ;
}
2017-01-10 05:40:37 -05:00
componentDidUpdate ( prevProps , prevState ) {
if ( this . state . searchTerm !== prevState . searchTerm ) {
this . resize ( ) ;
}
}
2015-10-16 12:32:19 +03:00
handleResize ( ) {
this . setState ( {
windowWidth : Utils . windowWidth ( ) ,
windowHeight : Utils . windowHeight ( )
} ) ;
2015-09-02 09:45:12 -07:00
}
2016-08-16 23:54:11 +05:00
onPreferenceChange ( ) {
this . setState ( {
2016-08-18 18:37:09 -04:00
compactDisplay : PreferenceStore . get ( Preferences . CATEGORY _DISPLAY _SETTINGS , Preferences . MESSAGE _DISPLAY , Preferences . MESSAGE _DISPLAY _DEFAULT ) === Preferences . MESSAGE _DISPLAY _COMPACT ,
flaggedPosts : PreferenceStore . getCategory ( Constants . Preferences . CATEGORY _FLAGGED _POST )
2016-08-16 23:54:11 +05:00
} ) ;
}
2015-09-02 09:45:12 -07:00
onChange ( ) {
if ( this . mounted ) {
2016-02-08 13:36:13 -05:00
this . setState ( getStateFromStores ( ) ) ;
2015-06-14 23:53:32 -08:00
}
2015-09-02 09:45:12 -07:00
}
2016-02-24 07:59:33 -05:00
onUserChange ( ) {
this . setState ( { profiles : JSON . parse ( JSON . stringify ( UserStore . getProfiles ( ) ) ) } ) ;
}
2016-12-22 17:19:19 +00:00
onBusy ( isBusy ) {
this . setState ( { isBusy } ) ;
}
onStatusChange ( ) {
this . setState ( { statuses : Object . assign ( { } , UserStore . getStatuses ( ) ) } ) ;
}
2015-09-02 09:45:12 -07:00
resize ( ) {
$ ( '#search-items-container' ) . scrollTop ( 0 ) ;
}
2015-06-14 23:53:32 -08:00
2015-09-02 09:45:12 -07:00
render ( ) {
2015-06-14 23:53:32 -08:00
var results = this . state . results ;
2015-07-13 04:23:24 -07:00
var noResults = ( ! results || ! results . order || ! results . order . length ) ;
2016-05-16 19:09:58 -04:00
const searchTerm = this . state . searchTerm ;
2016-02-24 07:59:33 -05:00
const profiles = this . state . profiles || { } ;
2016-08-04 11:38:09 -04:00
const flagIcon = Constants . FLAG _ICON _SVG ;
2015-06-14 23:53:32 -08:00
2015-09-02 09:45:12 -07:00
var ctls = null ;
2016-08-04 11:38:09 -04:00
if ( this . props . isFlaggedPosts && noResults ) {
ctls = (
< div className = 'sidebar--right__subheader' >
< ul >
< li >
< FormattedHTMLMessage
id = 'search_results.usageFlag1'
defaultMessage = "You haven't flagged any messages yet."
/ >
< / li >
< li >
< FormattedHTMLMessage
id = 'search_results.usageFlag2'
defaultMessage = 'You can add a flag to messages and comments by clicking the '
/ >
< span
className = 'usage__icon'
dangerouslySetInnerHTML = { { _ _html : flagIcon } }
/ >
< FormattedHTMLMessage
id = 'search_results.usageFlag3'
defaultMessage = ' icon next to the timestamp.'
/ >
< / li >
< li >
< FormattedHTMLMessage
id = 'search_results.usageFlag4'
defaultMessage = 'Flags are a way to mark messages for follow up. Your flags are personal, and cannot be seen by other users.'
/ >
< / li >
< / ul >
< / div >
) ;
2017-03-13 13:25:08 +01:00
} else if ( this . props . isPinnedPosts && noResults ) {
ctls = (
< div className = 'sidebar--right__subheader' >
< ul >
< li >
< FormattedHTMLMessage
id = 'search_results.usagePin1'
defaultMessage = 'There are no pinned messages yet.'
/ >
< / li >
< li >
< FormattedHTMLMessage
id = 'search_results.usagePin2'
2017-03-20 08:51:25 -04:00
defaultMessage = 'All members of this channel can pin important or useful messages.'
2017-03-13 13:25:08 +01:00
/ >
< / li >
< li >
< FormattedHTMLMessage
id = 'search_results.usagePin3'
2017-03-20 08:51:25 -04:00
defaultMessage = 'Pinned messages are visible to all channel members.'
/ >
< / li >
< li >
< FormattedHTMLMessage
id = 'search_results.usagePin4'
defaultMessage = { 'To pin a message: Go to the message that you want to pin and click [...] > "Pin to channel".' }
2017-03-13 13:25:08 +01:00
/ >
< / li >
< / ul >
< / div >
) ;
2016-08-04 11:38:09 -04:00
} else if ( ! searchTerm && noResults ) {
2015-11-18 09:33:08 -05:00
ctls = (
< div className = 'sidebar--right__subheader' >
2016-02-01 17:23:45 -03:00
< FormattedHTMLMessage
id = 'search_results.usage'
defaultMessage = '<ul><li>Use <b>"quotation marks"</b> to search for phrases</li><li>Use <b>from:</b> to find posts from specific users and <b>in:</b> to find posts in specific channels</li></ul>'
/ >
2015-11-18 09:33:08 -05:00
< / div >
) ;
} else if ( noResults ) {
2015-11-03 19:56:46 +05:00
ctls =
(
< div className = 'sidebar--right__subheader' >
2016-02-01 17:23:45 -03:00
< h4 >
< FormattedMessage
id = 'search_results.noResults'
2016-06-24 09:18:27 -04:00
defaultMessage = 'No results found. Try again?'
2016-02-01 17:23:45 -03:00
/ >
< / h4 >
< FormattedHTMLMessage
id = 'search_results.because'
defaultMessage = ' < ul >
2016-06-24 09:18:27 -04:00
< li > If you & # 39 ; re searching a partial phrase ( ex . searching "rea" , looking for "reach" or "reaction" ) , append a * to your search term . < / li >
< li > Two letter searches and common words like "this" , "a" and "is" won & # 39 ; t appear in search results , due to the excessive results returned . < / li >
2016-02-01 17:23:45 -03:00
< / ul > '
/ >
2015-11-03 19:56:46 +05:00
< / div >
) ;
2015-09-02 09:45:12 -07:00
} else {
2017-05-09 21:54:45 +09:00
ctls = results . order . map ( function searchResults ( id , idx , arr ) {
2016-02-25 19:02:30 -05:00
const post = results . posts [ id ] ;
let profile ;
if ( UserStore . getCurrentId ( ) === post . user _id ) {
profile = UserStore . getCurrentUser ( ) ;
} else {
profile = profiles [ post . user _id ] ;
}
2016-08-18 18:37:09 -04:00
2016-12-22 17:19:19 +00:00
let status = 'offline' ;
if ( this . state . statuses ) {
status = this . state . statuses [ post . user _id ] || 'offline' ;
}
2016-08-18 18:37:09 -04:00
let isFlagged = false ;
if ( this . state . flaggedPosts ) {
isFlagged = this . state . flaggedPosts . get ( post . id ) === 'true' ;
}
2017-05-09 21:54:45 +09:00
const reverseCount = arr . length - idx - 1 ;
2015-09-02 09:45:12 -07:00
return (
< SearchResultsItem
key = { post . id }
2016-02-08 13:36:13 -05:00
channel = { this . state . channels . get ( post . channel _id ) }
2016-08-16 23:54:11 +05:00
compactDisplay = { this . state . compactDisplay }
2015-09-02 09:45:12 -07:00
post = { post }
2017-05-09 21:54:45 +09:00
lastPostCount = { ( reverseCount >= 0 && reverseCount < Constants . TEST _ID _COUNT ) ? reverseCount : - 1 }
2016-02-25 19:02:30 -05:00
user = { profile }
2015-09-02 09:45:12 -07:00
term = { searchTerm }
isMentionSearch = { this . props . isMentionSearch }
2016-08-18 18:37:09 -04:00
isFlaggedSearch = { this . props . isFlaggedPosts }
2016-06-15 08:13:17 -04:00
useMilitaryTime = { this . props . useMilitaryTime }
2016-06-30 05:04:37 +05:00
shrink = { this . props . shrink }
2016-08-18 18:37:09 -04:00
isFlagged = { isFlagged }
2016-12-22 17:19:19 +00:00
isBusy = { this . state . isBusy }
status = { status }
2015-09-02 09:45:12 -07:00
/ >
) ;
} , this ) ;
}
2015-07-13 04:23:24 -07:00
2015-09-02 09:45:12 -07:00
return (
2017-03-24 18:05:43 +09:00
< div className = 'sidebar-right__body' >
< SearchResultsHeader
isMentionSearch = { this . props . isMentionSearch }
toggleSize = { this . props . toggleSize }
shrink = { this . props . shrink }
isFlaggedPosts = { this . props . isFlaggedPosts }
isPinnedPosts = { this . props . isPinnedPosts }
channelDisplayName = { this . props . channelDisplayName }
/ >
< div
id = 'search-items-container'
className = 'search-items-container'
>
{ ctls }
2015-06-14 23:53:32 -08:00
< / div >
< / div >
) ;
}
2015-09-02 09:45:12 -07:00
}
SearchResults . propTypes = {
2017-05-18 09:28:18 -04:00
isMentionSearch : PropTypes . bool ,
useMilitaryTime : PropTypes . bool . isRequired ,
toggleSize : PropTypes . func ,
shrink : PropTypes . func ,
isFlaggedPosts : PropTypes . bool ,
isPinnedPosts : PropTypes . bool ,
channelDisplayName : PropTypes . string . isRequired
2015-09-02 09:45:12 -07:00
} ;