parent
f87cce9e28
commit
49d8d72192
@ -1,38 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import Avatar from 'flavours/glitch/components/avatar';
|
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
|
||||||
import Permalink from 'flavours/glitch/components/permalink';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
export default class NavigationBar extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='navigation-bar'>
|
|
||||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
|
|
||||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
|
||||||
<Avatar account={this.props.account} size={40} />
|
|
||||||
</Permalink>
|
|
||||||
|
|
||||||
<div className='navigation-bar__profile'>
|
|
||||||
<Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
|
|
||||||
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
|
|
||||||
</Permalink>
|
|
||||||
|
|
||||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<IconButton title='' icon='close' onClick={this.props.onClose} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
|
||||||
import Motion from 'flavours/glitch/util/optional_motion';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
|
|
||||||
});
|
|
||||||
|
|
||||||
class SearchPopout extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
style: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { style } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ ...style, position: 'absolute', width: 285 }}>
|
|
||||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
|
||||||
{({ opacity, scaleX, scaleY }) => (
|
|
||||||
<div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Motion>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectIntl
|
|
||||||
export default class Search extends React.PureComponent {
|
|
||||||
|
|
||||||
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,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
expanded: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = (e) => {
|
|
||||||
this.props.onChange(e.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClear = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (this.props.value.length > 0 || this.props.submitted) {
|
|
||||||
this.props.onClear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown = (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onSubmit();
|
|
||||||
} else if (e.key === 'Escape') {
|
|
||||||
document.querySelector('.ui').parentElement.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noop () {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFocus = () => {
|
|
||||||
this.setState({ expanded: true });
|
|
||||||
this.props.onShow();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlur = () => {
|
|
||||||
this.setState({ expanded: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { intl, value, submitted } = this.props;
|
|
||||||
const { expanded } = this.state;
|
|
||||||
const hasValue = value.length > 0 || submitted;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='search'>
|
|
||||||
<label>
|
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
|
|
||||||
<input
|
|
||||||
className='search__input'
|
|
||||||
type='text'
|
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
|
||||||
value={value}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
onKeyUp={this.handleKeyDown}
|
|
||||||
onFocus={this.handleFocus}
|
|
||||||
onBlur={this.handleBlur}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
|
|
||||||
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
|
|
||||||
<i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
|
|
||||||
<SearchPopout />
|
|
||||||
</Overlay>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
|
||||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
export default class SearchResults extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
results: ImmutablePropTypes.map.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { results } = this.props;
|
|
||||||
|
|
||||||
let accounts, statuses, hashtags;
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
if (results.get('accounts') && results.get('accounts').size > 0) {
|
|
||||||
count += results.get('accounts').size;
|
|
||||||
accounts = (
|
|
||||||
<div className='search-results__section'>
|
|
||||||
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.get('statuses') && results.get('statuses').size > 0) {
|
|
||||||
count += results.get('statuses').size;
|
|
||||||
statuses = (
|
|
||||||
<div className='search-results__section'>
|
|
||||||
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.get('hashtags') && results.get('hashtags').size > 0) {
|
|
||||||
count += results.get('hashtags').size;
|
|
||||||
hashtags = (
|
|
||||||
<div className='search-results__section'>
|
|
||||||
{results.get('hashtags').map(hashtag =>
|
|
||||||
<Link key={hashtag} className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
|
|
||||||
#{hashtag}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='search-results'>
|
|
||||||
<div className='search-results__header'>
|
|
||||||
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{accounts}
|
|
||||||
{statuses}
|
|
||||||
{hashtags}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import NavigationBar from '../components/navigation_bar';
|
|
||||||
import { me } from 'flavours/glitch/util/initial_state';
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
account: state.getIn(['accounts', me]),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(NavigationBar);
|
|
@ -1,35 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
changeSearch,
|
|
||||||
clearSearch,
|
|
||||||
submitSearch,
|
|
||||||
showSearch,
|
|
||||||
} from 'flavours/glitch/actions/search';
|
|
||||||
import Search from '../components/search';
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
value: state.getIn(['search', 'value']),
|
|
||||||
submitted: state.getIn(['search', 'submitted']),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
|
|
||||||
onChange (value) {
|
|
||||||
dispatch(changeSearch(value));
|
|
||||||
},
|
|
||||||
|
|
||||||
onClear () {
|
|
||||||
dispatch(clearSearch());
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubmit () {
|
|
||||||
dispatch(submitSearch());
|
|
||||||
},
|
|
||||||
|
|
||||||
onShow () {
|
|
||||||
dispatch(showSearch());
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
|
@ -1,8 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import SearchResults from '../components/search_results';
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
results: state.getIn(['search', 'results']),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(SearchResults);
|
|
@ -0,0 +1,117 @@
|
|||||||
|
// Package imports.
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Components.
|
||||||
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
|
|
||||||
|
// Utils.
|
||||||
|
import { conditionalRender } from 'flavours/glitch/util/react_helpers';
|
||||||
|
|
||||||
|
// Messages.
|
||||||
|
const messages = defineMessages({
|
||||||
|
community: {
|
||||||
|
defaultMessage: 'Local timeline',
|
||||||
|
id: 'navigation_bar.community_timeline',
|
||||||
|
},
|
||||||
|
home_timeline: {
|
||||||
|
defaultMessage: 'Home',
|
||||||
|
id: 'tabs_bar.home',
|
||||||
|
},
|
||||||
|
logout: {
|
||||||
|
defaultMessage: 'Logout',
|
||||||
|
id: 'navigation_bar.logout',
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
defaultMessage: 'Notifications',
|
||||||
|
id: 'tabs_bar.notifications',
|
||||||
|
},
|
||||||
|
public: {
|
||||||
|
defaultMessage: 'Federated timeline',
|
||||||
|
id: 'navigation_bar.public_timeline',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
defaultMessage: 'App settings',
|
||||||
|
id: 'navigation_bar.app_settings',
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
defaultMessage: 'Getting started',
|
||||||
|
id: 'getting_started.heading',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// The component.
|
||||||
|
export default function DrawerHeader ({
|
||||||
|
columns,
|
||||||
|
intl,
|
||||||
|
onSettingsClick,
|
||||||
|
}) {
|
||||||
|
|
||||||
|
// Only renders the component if the column isn't being shown.
|
||||||
|
const renderForColumn = conditionalRender.bind(
|
||||||
|
columnId => !columns || !columns.some(
|
||||||
|
column => column.get('id') === columnId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// The result.
|
||||||
|
return (
|
||||||
|
<nav className='drawer--header'>
|
||||||
|
<Link
|
||||||
|
aria-label={intl.formatMessage(messages.start)}
|
||||||
|
title={intl.formatMessage(messages.start)}
|
||||||
|
to='/getting-started'
|
||||||
|
><Icon icon='asterisk' /></Link>
|
||||||
|
{renderForColumn('HOME', (
|
||||||
|
<Link
|
||||||
|
aria-label={intl.formatMessage(messages.home_timeline)}
|
||||||
|
title={intl.formatMessage(messages.home_timeline)}
|
||||||
|
to='/timelines/home'
|
||||||
|
><Icon icon='home' /></Link>
|
||||||
|
))}
|
||||||
|
{renderForColumn('NOTIFICATIONS', (
|
||||||
|
<Link
|
||||||
|
aria-label={intl.formatMessage(messages.notifications)}
|
||||||
|
title={intl.formatMessage(messages.notifications)}
|
||||||
|
to='/notifications'
|
||||||
|
><Icon icon='bell' /></Link>
|
||||||
|
))}
|
||||||
|
{renderForColumn('COMMUNITY', (
|
||||||
|
<Link
|
||||||
|
aria-label={intl.formatMessage(messages.community)}
|
||||||
|
title={intl.formatMessage(messages.community)}
|
||||||
|
to='/timelines/public/local'
|
||||||
|
><Icon icon='users' /></Link>
|
||||||
|
))}
|
||||||
|
{renderForColumn('PUBLIC', (
|
||||||
|
<Link
|
||||||
|
aria-label={intl.formatMessage(messages.public)}
|
||||||
|
title={intl.formatMessage(messages.public)}
|
||||||
|
to='/timelines/public'
|
||||||
|
><Icon icon='globe' /></Link>
|
||||||
|
))}
|
||||||
|
<a
|
||||||
|
aria-label={intl.formatMessage(messages.settings)}
|
||||||
|
onClick={onSettingsClick}
|
||||||
|
role='button'
|
||||||
|
title={intl.formatMessage(messages.settings)}
|
||||||
|
tabIndex='0'
|
||||||
|
><Icon icon='cogs' /></a>
|
||||||
|
<a
|
||||||
|
aria-label={intl.formatMessage(messages.logout)}
|
||||||
|
data-method='delete'
|
||||||
|
href='/auth/sign_out'
|
||||||
|
title={intl.formatMessage(messages.logout)}
|
||||||
|
><Icon icon='sign-out' /></a>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerHeader.propTypes = {
|
||||||
|
columns: ImmutablePropTypes.list,
|
||||||
|
intl: PropTypes.object,
|
||||||
|
onSettingsClick: PropTypes.func,
|
||||||
|
};
|
@ -0,0 +1,70 @@
|
|||||||
|
// Package imports.
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import {
|
||||||
|
FormattedMessage,
|
||||||
|
defineMessages,
|
||||||
|
} from 'react-intl';
|
||||||
|
|
||||||
|
// Components.
|
||||||
|
import Avatar from 'flavours/glitch/components/avatar';
|
||||||
|
import Permalink from 'flavours/glitch/components/permalink';
|
||||||
|
|
||||||
|
// Utils.
|
||||||
|
import { hiddenComponent } from 'flavours/glitch/util/react_helpers';
|
||||||
|
|
||||||
|
// Messages.
|
||||||
|
const messages = defineMessages({
|
||||||
|
edit: {
|
||||||
|
defaultMessage: 'Edit profile',
|
||||||
|
id: 'navigation_bar.edit_profile',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// The component.
|
||||||
|
export default function DrawerPagerAccount ({ account }) {
|
||||||
|
|
||||||
|
// We need an account to render.
|
||||||
|
if (!account) {
|
||||||
|
return (
|
||||||
|
<div className='drawer--pager--account'>
|
||||||
|
<a
|
||||||
|
className='edit'
|
||||||
|
href='/settings/profile'
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.edit} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The result.
|
||||||
|
return (
|
||||||
|
<div className='drawer--pager--account'>
|
||||||
|
<Permalink
|
||||||
|
className='avatar'
|
||||||
|
href={account.get('url')}
|
||||||
|
to={`/accounts/${account.get('id')}`}
|
||||||
|
>
|
||||||
|
<span {...hiddenComponent}>{account.get('acct')}</span>
|
||||||
|
<Avatar
|
||||||
|
account={account}
|
||||||
|
size={40}
|
||||||
|
/>
|
||||||
|
</Permalink>
|
||||||
|
<Permalink
|
||||||
|
className='acct'
|
||||||
|
href={account.get('url')}
|
||||||
|
to={`/accounts/${account.get('id')}`}
|
||||||
|
>
|
||||||
|
<strong>@{account.get('acct')}</strong>
|
||||||
|
</Permalink>
|
||||||
|
<a
|
||||||
|
className='edit'
|
||||||
|
href='/settings/profile'
|
||||||
|
><FormattedMessage {...messages.edit} /></a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerPagerAccount.propTypes = { account: ImmutablePropTypes.map };
|
@ -0,0 +1,43 @@
|
|||||||
|
// Package imports.
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
|
// Components.
|
||||||
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
|
import Composer from 'flavours/glitch/features/composer';
|
||||||
|
import DrawerPagerAccount from './account';
|
||||||
|
|
||||||
|
// The component.
|
||||||
|
export default function DrawerPager ({
|
||||||
|
account,
|
||||||
|
active,
|
||||||
|
onClose,
|
||||||
|
onFocus,
|
||||||
|
}) {
|
||||||
|
const computedClass = classNames('drawer--pager', { active });
|
||||||
|
|
||||||
|
// The result.
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={computedClass}
|
||||||
|
onFocus={onFocus}
|
||||||
|
>
|
||||||
|
<DrawerPagerAccount account={account} />
|
||||||
|
<IconButton
|
||||||
|
icon='close'
|
||||||
|
onClick={onClose}
|
||||||
|
title=''
|
||||||
|
/>
|
||||||
|
<Composer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerPager.propTypes = {
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
|
active: PropTypes.bool,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
onFocus: PropTypes.func,
|
||||||
|
};
|
@ -0,0 +1,114 @@
|
|||||||
|
// Package imports.
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import {
|
||||||
|
FormattedMessage,
|
||||||
|
defineMessages,
|
||||||
|
} from 'react-intl';
|
||||||
|
import spring from 'react-motion/lib/spring';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Components.
|
||||||
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
|
import StatusContainer from 'flavours/glitch/containers/status_container';
|
||||||
|
|
||||||
|
// Utils.
|
||||||
|
import Motion from 'flavours/glitch/util/optional_motion';
|
||||||
|
|
||||||
|
// Messages.
|
||||||
|
const messages = defineMessages({
|
||||||
|
total: {
|
||||||
|
defaultMessage: '{count, number} {count, plural, one {result} other {results}}',
|
||||||
|
id: 'search_results.total',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// The component.
|
||||||
|
export default function DrawerPager ({
|
||||||
|
results,
|
||||||
|
visible,
|
||||||
|
}) {
|
||||||
|
const accounts = results ? results.get('accounts') : null;
|
||||||
|
const statuses = results ? results.get('statuses') : null;
|
||||||
|
const hashtags = results ? results.get('hashtags') : null;
|
||||||
|
|
||||||
|
const count = [accounts, statuses, hashtags].reduce(function (size, item) {
|
||||||
|
if (item && item.size) {
|
||||||
|
return size + item.size;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// The result.
|
||||||
|
return (
|
||||||
|
<Motion
|
||||||
|
defaultStyle={{ x: -100 }}
|
||||||
|
style={{
|
||||||
|
x: spring(visible ? 0 : -100, {
|
||||||
|
stiffness: 210,
|
||||||
|
damping: 20,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ x }) => (
|
||||||
|
<div
|
||||||
|
className='drawer--results'
|
||||||
|
style={{
|
||||||
|
transform: `translateX(${x}%)`,
|
||||||
|
visibility: x === -100 ? 'hidden' : 'visible',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<header>
|
||||||
|
<FormattedMessage
|
||||||
|
{...messages.total}
|
||||||
|
values={{ count }}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
{accounts && accounts.size ? (
|
||||||
|
<section>
|
||||||
|
{accounts.map(
|
||||||
|
accountId => (
|
||||||
|
<AccountContainer
|
||||||
|
id={accountId}
|
||||||
|
key={accountId}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
{statuses && statuses.size ? (
|
||||||
|
<section>
|
||||||
|
{statuses.map(
|
||||||
|
statusId => (
|
||||||
|
<StatusContainer
|
||||||
|
id={statusId}
|
||||||
|
key={statusId}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
{hashtags && hashtags.size ? (
|
||||||
|
<section>
|
||||||
|
{hashtags.map(
|
||||||
|
hashtag => (
|
||||||
|
<Link
|
||||||
|
className='hashtag'
|
||||||
|
key={hashtag}
|
||||||
|
to={`/timelines/tag/${hashtag}`}
|
||||||
|
>#{hashtag}</Link>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Motion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerPager.propTypes = {
|
||||||
|
results: ImmutablePropTypes.map,
|
||||||
|
visible: PropTypes.bool,
|
||||||
|
};
|
@ -0,0 +1,149 @@
|
|||||||
|
// Package imports.
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage,
|
||||||
|
defineMessages,
|
||||||
|
} from 'react-intl';
|
||||||
|
import Overlay from 'react-overlays/lib/Overlay';
|
||||||
|
|
||||||
|
// Components.
|
||||||
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
|
import DrawerSearchPopout from './popout';
|
||||||
|
|
||||||
|
// Utils.
|
||||||
|
import { focusRoot } from 'flavours/glitch/util/dom_helpers';
|
||||||
|
import {
|
||||||
|
assignHandlers,
|
||||||
|
hiddenComponent,
|
||||||
|
} from 'flavours/glitch/util/react_helpers';
|
||||||
|
|
||||||
|
// Messages.
|
||||||
|
const messages = defineMessages({
|
||||||
|
placeholder: {
|
||||||
|
defaultMessage: 'Search',
|
||||||
|
id: 'search.placeholder',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handlers.
|
||||||
|
const handlers = {
|
||||||
|
|
||||||
|
blur () {
|
||||||
|
this.setState({ expanded: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
change ({ target: { value } }) {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
if (onChange) {
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clear (e) {
|
||||||
|
const {
|
||||||
|
onClear,
|
||||||
|
submitted,
|
||||||
|
value: { length },
|
||||||
|
} = this.props;
|
||||||
|
e.preventDefault(); // Prevents focus change ??
|
||||||
|
if (onClear && (submitted || length)) {
|
||||||
|
onClear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
const { onShow } = this.props;
|
||||||
|
this.setState({ expanded: true });
|
||||||
|
if (onShow) {
|
||||||
|
onShow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keyUp (e) {
|
||||||
|
const { onSubmit } = this.props;
|
||||||
|
switch (e.key) {
|
||||||
|
case 'Enter':
|
||||||
|
if (onSubmit) {
|
||||||
|
onSubmit();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
focusRoot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// The component.
|
||||||
|
export default class DrawerSearch extends React.PureComponent {
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
assignHandlers(this, handlers);
|
||||||
|
this.state = { expanded: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
blur,
|
||||||
|
change,
|
||||||
|
clear,
|
||||||
|
focus,
|
||||||
|
keyUp,
|
||||||
|
} = this.handlers;
|
||||||
|
const {
|
||||||
|
intl,
|
||||||
|
submitted,
|
||||||
|
value,
|
||||||
|
} = this.props;
|
||||||
|
const { expanded } = this.state;
|
||||||
|
const computedClass = classNames('drawer--search', { active: value.length || submitted });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={computedClass}>
|
||||||
|
<label>
|
||||||
|
<span {...hiddenComponent}>
|
||||||
|
<FormattedMessage {...messages.placeholder} />
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
|
value={value}
|
||||||
|
onChange={change}
|
||||||
|
onKeyUp={keyUp}
|
||||||
|
onFocus={focus}
|
||||||
|
onBlur={blur}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
aria-label={intl.formatMessage(messages.placeholder)}
|
||||||
|
className='icon'
|
||||||
|
onClick={clear}
|
||||||
|
role='button'
|
||||||
|
tabIndex='0'
|
||||||
|
>
|
||||||
|
<Icon icon='search' />
|
||||||
|
<Icon icon='fa-times-circle' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Overlay
|
||||||
|
placement='bottom'
|
||||||
|
show={expanded && !value.length && !submitted}
|
||||||
|
target={this}
|
||||||
|
><DrawerSearchPopout /></Overlay>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerSearch.propTypes = {
|
||||||
|
value: PropTypes.string,
|
||||||
|
submitted: PropTypes.bool,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
onClear: PropTypes.func,
|
||||||
|
onShow: PropTypes.func,
|
||||||
|
intl: PropTypes.object,
|
||||||
|
};
|
@ -0,0 +1,95 @@
|
|||||||
|
// Package imports.
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage,
|
||||||
|
defineMessages,
|
||||||
|
} from 'react-intl';
|
||||||
|
import spring from 'react-motion/lib/spring';
|
||||||
|
|
||||||
|
// Utils.
|
||||||
|
import Motion from 'flavours/glitch/util/optional_motion';
|
||||||
|
|
||||||
|
// Messages.
|
||||||
|
const messages = defineMessages({
|
||||||
|
format: {
|
||||||
|
defaultMessage: 'Advanced search format',
|
||||||
|
id: 'search_popout.search_format',
|
||||||
|
},
|
||||||
|
hashtag: {
|
||||||
|
defaultMessage: 'hashtag',
|
||||||
|
id: 'search_popout.tips.hashtag',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
defaultMessage: 'status',
|
||||||
|
id: 'search_popout.tips.status',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
defaultMessage: 'Simple text returns matching display names, usernames and hashtags',
|
||||||
|
id: 'search_popout.tips.text',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
defaultMessage: 'user',
|
||||||
|
id: 'search_popout.tips.user',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const motionSpring = spring(1, { damping: 35, stiffness: 400 });
|
||||||
|
|
||||||
|
export default function DrawerSearchPopout ({ style }) {
|
||||||
|
return (
|
||||||
|
<Motion
|
||||||
|
defaultStyle={{
|
||||||
|
opacity: 0,
|
||||||
|
scaleX: 0.85,
|
||||||
|
scaleY: 0.75,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
opacity: motionSpring,
|
||||||
|
scaleX: motionSpring,
|
||||||
|
scaleY: motionSpring,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ opacity, scaleX, scaleY }) => (
|
||||||
|
<div
|
||||||
|
className='drawer--search--popout'
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
position: 'absolute',
|
||||||
|
width: 285,
|
||||||
|
opacity: opacity,
|
||||||
|
transform: `scale(${scaleX}, ${scaleY})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h4><FormattedMessage {...messages.format} /></h4>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<em>#example</em>
|
||||||
|
{' '}
|
||||||
|
<FormattedMessage {...messages.hashtag} />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<em>@username@domain</em>
|
||||||
|
{' '}
|
||||||
|
<FormattedMessage {...messages.user} />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<em>URL</em>
|
||||||
|
{' '}
|
||||||
|
<FormattedMessage {...messages.user} />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<em>URL</em>
|
||||||
|
{' '}
|
||||||
|
<FormattedMessage {...messages.status} />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<FormattedMessage {...messages.text} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Motion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props.
|
||||||
|
DrawerSearchPopout.propTypes = { style: PropTypes.object };
|
Loading…
Reference in new issue