diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js
index 25c2622d86..506c668a7f 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/composer/index.js
@@ -2,9 +2,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
-import { injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router';
// Actions.
import {
@@ -43,7 +40,7 @@ import { countableText } from 'flavours/glitch/util/counter';
import { me } from 'flavours/glitch/util/initial_state';
import { isMobile } from 'flavours/glitch/util/is_mobile';
import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { mergeProps } from 'flavours/glitch/util/redux_helpers';
+import { wrap } from 'flavours/glitch/util/redux_helpers';
// State mapping.
function mapStateToProps (state) {
@@ -204,9 +201,7 @@ const handlers = {
};
// The component.
-@injectIntl
-@connect(mapStateToProps, mapDispatchToProps, mergeProps)
-export default class Composer extends React.Component {
+class Composer extends React.Component {
// Constructor.
constructor (props) {
@@ -408,7 +403,7 @@ export default class Composer extends React.Component {
// Context
Composer.contextTypes = {
history: PropTypes.object,
-}
+};
// Props.
Composer.propTypes = {
@@ -438,3 +433,7 @@ Composer.propTypes = {
text: PropTypes.string,
}).isRequired,
};
+
+// Connecting and export.
+export { Composer as WrappedComponent };
+export default wrap(Composer, mapStateToProps, mapDispatchToProps, true);
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
index 0f304bc88e..ee52008a75 100644
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
@@ -70,7 +70,7 @@ const handlers = {
// dropdown.
if (onModalClose && isUserTouching()) {
if (open) {
- onModalClose()
+ onModalClose();
} else if (onChange && onModalOpen) {
onModalOpen({
actions: items.map(
diff --git a/app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js b/app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js
deleted file mode 100644
index 1b6d74123e..0000000000
--- a/app/javascript/flavours/glitch/features/drawer/components/navigation_bar.js
+++ /dev/null
@@ -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 (
-
- {multiColumn ? (
-
- ) : null}
-
-
-
-
-
-
-
- {({ x }) => (
-
- )}
-
+ // Rendering.
+ render () {
+ const {
+ dispatch: {
+ change,
+ changeComposingOff,
+ changeComposingOn,
+ clear,
+ openSettings,
+ show,
+ submit,
+ },
+ intl,
+ multiColumn,
+ state: {
+ account,
+ columns,
+ isComposing,
+ results,
+ searchHidden,
+ searchValue,
+ submitted,
+ },
+ } = this.props;
+
+ // The result.
+ return (
+
+ {multiColumn ? (
+
+ ) : null}
+
+
+
-
- );
+ );
+ }
+
}
// Props.
Drawer.propTypes = {
dispatch: PropTypes.func.isRequired,
- columns: ImmutablePropTypes.list.isRequired,
- multiColumn: PropTypes.bool,
- showSearch: PropTypes.bool,
intl: PropTypes.object.isRequired,
+ multiColumn: PropTypes.bool,
+ state: PropTypes.shape({
+ account: ImmutablePropTypes.map,
+ columns: ImmutablePropTypes.list,
+ isComposing: PropTypes.bool,
+ results: ImmutablePropTypes.map,
+ searchHidden: PropTypes.bool,
+ searchValue: PropTypes.string,
+ submitted: PropTypes.bool,
+ }).isRequired,
};
+
+// Connecting and export.
+export { Drawer as WrappedComponent };
+export default wrap(Drawer, mapStateToProps, mapDispatchToProps, true);
diff --git a/app/javascript/flavours/glitch/features/drawer/pager/account/index.js b/app/javascript/flavours/glitch/features/drawer/pager/account/index.js
new file mode 100644
index 0000000000..2ee95d5b9c
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/drawer/pager/account/index.js
@@ -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 (
+
+ );
+ }
+
+ // The result.
+ return (
+
+
+ {account.get('acct')}
+
+
+
+ @{account.get('acct')}
+
+
+
+ );
+}
+
+DrawerPagerAccount.propTypes = { account: ImmutablePropTypes.map };
diff --git a/app/javascript/flavours/glitch/features/drawer/pager/index.js b/app/javascript/flavours/glitch/features/drawer/pager/index.js
new file mode 100644
index 0000000000..8dc2d3ee93
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/drawer/pager/index.js
@@ -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 (
+
+
+
+
+
+ );
+}
+
+DrawerPager.propTypes = {
+ account: ImmutablePropTypes.map,
+ active: PropTypes.bool,
+ onClose: PropTypes.func,
+ onFocus: PropTypes.func,
+};
diff --git a/app/javascript/flavours/glitch/features/drawer/results/index.js b/app/javascript/flavours/glitch/features/drawer/results/index.js
new file mode 100644
index 0000000000..559d56da5c
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/drawer/results/index.js
@@ -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 (
+
+ {({ x }) => (
+
+
+ {accounts && accounts.size ? (
+
+ {accounts.map(
+ accountId => (
+
+ )
+ )}
+
+ ) : null}
+ {statuses && statuses.size ? (
+
+ {statuses.map(
+ statusId => (
+
+ )
+ )}
+
+ ) : null}
+ {hashtags && hashtags.size ? (
+
+ {hashtags.map(
+ hashtag => (
+ #{hashtag}
+ )
+ )}
+
+ ) : null}
+
+ )}
+
+ );
+}
+
+DrawerPager.propTypes = {
+ results: ImmutablePropTypes.map,
+ visible: PropTypes.bool,
+};
diff --git a/app/javascript/flavours/glitch/features/drawer/search/index.js b/app/javascript/flavours/glitch/features/drawer/search/index.js
new file mode 100644
index 0000000000..ccb2ba8591
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/drawer/search/index.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+}
+
+DrawerSearch.propTypes = {
+ value: PropTypes.string,
+ submitted: PropTypes.bool,
+ onChange: PropTypes.func,
+ onSubmit: PropTypes.func,
+ onClear: PropTypes.func,
+ onShow: PropTypes.func,
+ intl: PropTypes.object,
+};
diff --git a/app/javascript/flavours/glitch/features/drawer/search/popout/index.js b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js
new file mode 100644
index 0000000000..bd36275f51
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/drawer/search/popout/index.js
@@ -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 (
+
+ {({ opacity, scaleX, scaleY }) => (
+
+
+
+ -
+ #example
+ {' '}
+
+
+ -
+ @username@domain
+ {' '}
+
+
+ -
+ URL
+ {' '}
+
+
+ -
+ URL
+ {' '}
+
+
+
+
+
+ )}
+
+ );
+}
+
+// Props.
+DrawerSearchPopout.propTypes = { style: PropTypes.object };
diff --git a/app/javascript/flavours/glitch/util/dom_helpers.js b/app/javascript/flavours/glitch/util/dom_helpers.js
index ee95ef8dd5..3e1f4a26d7 100644
--- a/app/javascript/flavours/glitch/util/dom_helpers.js
+++ b/app/javascript/flavours/glitch/util/dom_helpers.js
@@ -4,3 +4,11 @@ import detectPassiveEvents from 'detect-passive-events';
// This will either be a passive lister options object (if passive
// events are supported), or `false`.
export const withPassive = detectPassiveEvents.hasSupport ? { passive: true } : false;
+
+// Focuses the root element.
+export function focusRoot () {
+ let e;
+ if (document && (e = document.querySelector('.ui')) && (e = e.parentElement)) {
+ e.focus();
+ }
+}
diff --git a/app/javascript/flavours/glitch/util/react_helpers.js b/app/javascript/flavours/glitch/util/react_helpers.js
index 0826f3584f..087e3969d0 100644
--- a/app/javascript/flavours/glitch/util/react_helpers.js
+++ b/app/javascript/flavours/glitch/util/react_helpers.js
@@ -14,7 +14,7 @@ export function assignHandlers (target, handlers) {
// This function only returns the component if the result of calling
// `test` with `data` is `true`. Useful with funciton binding.
export function conditionalRender (test, data, component) {
- return test ? component : null;
+ return test(data) ? component : null;
}
// This object provides props to make the component not visible.
diff --git a/app/javascript/flavours/glitch/util/redux_helpers.js b/app/javascript/flavours/glitch/util/redux_helpers.js
index 3bc8bc86fb..c0f5eeb28b 100644
--- a/app/javascript/flavours/glitch/util/redux_helpers.js
+++ b/app/javascript/flavours/glitch/util/redux_helpers.js
@@ -1,3 +1,6 @@
+import { injectIntl } from 'react-intl';
+import { connect } from 'react-redux';
+
// Merges react-redux props.
export function mergeProps (stateProps, dispatchProps, ownProps) {
Object.assign({}, ownProps, {
@@ -5,3 +8,9 @@ export function mergeProps (stateProps, dispatchProps, ownProps) {
state: Object.assign({}, stateProps, ownProps.state || {}),
});
}
+
+// Connects a component.
+export function wrap (Component, mapStateToProps, mapDispatchToProps, options) {
+ const withIntl = typeof options === 'object' ? options.withIntl : !!options;
+ return (withIntl ? injectIntl : i => i)(connect(mapStateToProps, mapDispatchToProps, mergeProps)(Component));
+}