2017-12-26 19:54:28 -05:00
// Package imports.
import React from 'react' ;
2023-05-07 15:43:25 -04:00
import PropTypes from 'prop-types' ;
2017-12-26 19:54:28 -05:00
import {
2019-04-19 14:57:43 -04:00
injectIntl ,
2019-04-19 15:05:18 -04:00
FormattedMessage ,
2017-12-26 19:54:28 -05:00
defineMessages ,
} from 'react-intl' ;
2023-01-11 15:58:46 -05:00
import Overlay from 'react-overlays/Overlay' ;
2017-12-26 19:54:28 -05:00
// Components.
2023-05-08 21:11:56 -04:00
import { Icon } from 'flavours/glitch/components/icon' ;
2017-12-26 19:54:28 -05:00
// Utils.
2022-10-11 04:41:15 -04:00
import { focusRoot } from 'flavours/glitch/utils/dom_helpers' ;
2022-10-11 04:17:04 -04:00
import { searchEnabled } from 'flavours/glitch/initial_state' ;
2017-12-26 19:54:28 -05:00
const messages = defineMessages ( {
2019-04-19 15:05:18 -04:00
placeholder : { id : 'search.placeholder' , defaultMessage : 'Search' } ,
2022-10-29 07:32:49 -04:00
placeholderSignedIn : { id : 'search.search_or_paste' , defaultMessage : 'Search or paste URL' } ,
2017-12-26 19:54:28 -05:00
} ) ;
2019-04-19 15:05:18 -04:00
class SearchPopout extends React . PureComponent {
render ( ) {
const extraInformation = searchEnabled ? < FormattedMessage id = 'search_popout.tips.full_text' defaultMessage = 'Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' / > : < FormattedMessage id = 'search_popout.tips.text' defaultMessage = 'Simple text returns matching display names, usernames and hashtags' / > ;
return (
2023-01-11 15:58:46 -05:00
< div className = 'search-popout' >
< h4 > < FormattedMessage id = 'search_popout.search_format' defaultMessage = 'Advanced search format' / > < / h4 >
< ul >
< li > < em > # example < / em > < FormattedMessage id = 'search_popout.tips.hashtag' defaultMessage = 'hashtag' / > < / li >
< li > < em > @ username @ domain < / em > < FormattedMessage id = 'search_popout.tips.user' defaultMessage = 'user' / > < / li >
< li > < em > URL < / em > < FormattedMessage id = 'search_popout.tips.user' defaultMessage = 'user' / > < / li >
< li > < em > URL < / em > < FormattedMessage id = 'search_popout.tips.status' defaultMessage = 'status' / > < / li >
< / ul >
{ extraInformation }
2019-04-19 15:05:18 -04:00
< / div >
) ;
}
}
2019-04-19 14:57:43 -04:00
// The component.
2019-04-20 11:50:12 -04:00
class Search extends React . PureComponent {
2017-12-26 19:54:28 -05:00
2019-05-25 15:27:00 -04:00
static contextTypes = {
router : PropTypes . object . isRequired ,
2022-10-29 07:32:49 -04:00
identity : PropTypes . object . isRequired ,
2019-05-25 15:27:00 -04:00
} ;
2019-04-19 14:57:43 -04:00
static propTypes = {
value : PropTypes . string . isRequired ,
submitted : PropTypes . bool ,
onChange : PropTypes . func . isRequired ,
onSubmit : PropTypes . func . isRequired ,
onClear : PropTypes . func . isRequired ,
onShow : PropTypes . func . isRequired ,
2019-05-25 15:27:00 -04:00
openInRoute : PropTypes . bool ,
2019-04-19 14:57:43 -04:00
intl : PropTypes . object . isRequired ,
2019-10-01 13:19:10 -04:00
singleColumn : PropTypes . bool ,
2019-04-19 14:57:43 -04:00
} ;
state = {
expanded : false ,
} ;
2017-12-26 19:54:28 -05:00
2019-10-01 13:19:10 -04:00
setRef = c => {
this . searchForm = c ;
2023-02-03 14:52:07 -05:00
} ;
2019-10-01 13:19:10 -04:00
2019-04-19 14:57:43 -04:00
handleChange = ( e ) => {
2017-12-26 19:54:28 -05:00
const { onChange } = this . props ;
if ( onChange ) {
2019-04-19 14:57:43 -04:00
onChange ( e . target . value ) ;
2017-12-26 19:54:28 -05:00
}
2023-02-03 14:52:07 -05:00
} ;
2017-12-26 19:54:28 -05:00
2019-04-19 14:57:43 -04:00
handleClear = ( e ) => {
2017-12-26 19:54:28 -05:00
const {
onClear ,
submitted ,
2018-01-06 18:34:01 -05:00
value ,
2017-12-26 19:54:28 -05:00
} = this . props ;
e . preventDefault ( ) ; // Prevents focus change ??
2018-01-06 18:34:01 -05:00
if ( onClear && ( submitted || value && value . length ) ) {
2017-12-26 19:54:28 -05:00
onClear ( ) ;
}
2023-02-03 14:52:07 -05:00
} ;
2019-04-19 14:57:43 -04:00
2019-04-19 15:05:18 -04:00
handleBlur = ( ) => {
2019-04-19 14:57:43 -04:00
this . setState ( { expanded : false } ) ;
2023-02-03 14:52:07 -05:00
} ;
2017-12-26 19:54:28 -05:00
2019-04-19 14:57:43 -04:00
handleFocus = ( ) => {
2017-12-26 19:54:28 -05:00
this . setState ( { expanded : true } ) ;
2019-10-01 13:19:10 -04:00
this . props . onShow ( ) ;
if ( this . searchForm && ! this . props . singleColumn ) {
const { left , right } = this . searchForm . getBoundingClientRect ( ) ;
if ( left < 0 || right > ( window . innerWidth || document . documentElement . clientWidth ) ) {
this . searchForm . scrollIntoView ( ) ;
}
2017-12-26 19:54:28 -05:00
}
2023-02-03 14:52:07 -05:00
} ;
2017-12-26 19:54:28 -05:00
2019-04-19 14:57:43 -04:00
handleKeyUp = ( e ) => {
2017-12-26 19:54:28 -05:00
const { onSubmit } = this . props ;
switch ( e . key ) {
case 'Enter' :
2019-05-25 15:27:00 -04:00
onSubmit ( ) ;
if ( this . props . openInRoute ) {
this . context . router . history . push ( '/search' ) ;
2017-12-26 19:54:28 -05:00
}
break ;
case 'Escape' :
focusRoot ( ) ;
}
2023-02-03 14:52:07 -05:00
} ;
2017-12-26 19:54:28 -05:00
2023-01-11 15:58:46 -05:00
findTarget = ( ) => {
return this . searchForm ;
2023-02-03 14:52:07 -05:00
} ;
2023-01-11 15:58:46 -05:00
2017-12-26 19:54:28 -05:00
render ( ) {
2019-04-19 14:57:43 -04:00
const { intl , value , submitted } = this . props ;
2017-12-26 19:54:28 -05:00
const { expanded } = this . state ;
2022-10-29 07:32:49 -04:00
const { signedIn } = this . context . identity ;
2019-06-27 16:30:55 -04:00
const hasValue = value . length > 0 || submitted ;
2017-12-26 19:54:28 -05:00
return (
2019-06-27 16:30:55 -04:00
< div className = 'search' >
2022-12-15 10:20:21 -05:00
< input
ref = { this . setRef }
className = 'search__input'
type = 'text'
placeholder = { intl . formatMessage ( signedIn ? messages . placeholderSignedIn : messages . placeholder ) }
aria - label = { intl . formatMessage ( signedIn ? messages . placeholderSignedIn : messages . placeholder ) }
value = { value || '' }
onChange = { this . handleChange }
onKeyUp = { this . handleKeyUp }
onFocus = { this . handleFocus }
onBlur = { this . handleBlur }
/ >
2023-04-04 10:33:44 -04:00
< div role = 'button' tabIndex = { 0 } className = 'search__icon' onClick = { this . handleClear } >
2019-09-09 09:28:45 -04:00
< Icon id = 'search' className = { hasValue ? '' : 'active' } / >
< Icon id = 'times-circle' className = { hasValue ? 'active' : '' } / >
2017-12-26 19:54:28 -05:00
< / div >
2023-01-11 15:58:46 -05:00
< Overlay show = { expanded && ! hasValue } placement = 'bottom' target = { this . findTarget } popperConfig = { { strategy : 'fixed' } } >
{ ( { props , placement } ) => (
< div { ...props } style = { { ... props . style , width : 285 , zIndex : 2 } } >
< div className = { ` dropdown-animation ${ placement } ` } >
< SearchPopout / >
< / div >
< / div >
) }
2019-04-19 14:57:43 -04:00
< / Overlay >
2017-12-26 19:54:28 -05:00
< / div >
) ;
}
}
2023-03-24 18:15:25 -04:00
export default injectIntl ( Search ) ;