Another large update for all components

reorganization, linting, updating file imports, consolidation
warning: there will be errors in this commit
todo: update webpack, add missing styles, scss files, consolidate group page components.
This commit is contained in:
mgabdev
2019-08-09 12:06:27 -04:00
parent 280dc51d85
commit 3d509c84a2
183 changed files with 4802 additions and 2361 deletions

View File

@@ -1,7 +1,7 @@
import { openModal } from '../../../actions/modal';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
import { defineMessages, injectIntl } from 'react-intl';
import { meUsername } from 'gabsocial/initial_state';
import { openModal } from '../../../../actions/modal';
import DropdownMenuContainer from '../../../../containers/dropdown_menu_container';
import { meUsername } from '../../../../initial_state';
const messages = defineMessages({
profile: { id: 'account.profile', defaultMessage: 'Profile' },
@@ -33,7 +33,7 @@ class ActionBar extends PureComponent {
}
render () {
const { intl, onOpenHotkeys } = this.props;
const { intl } = this.props;
const size = this.props.size || 16;
let menu = [];
@@ -51,8 +51,8 @@ class ActionBar extends PureComponent {
menu.push({ text: intl.formatMessage(messages.logout), href: '/auth/sign_out', isLogout: true });
return (
<div className='compose__action-bar' style={{'marginTop':'-6px'}}>
<div className='compose__action-bar-dropdown'>
<div style={{'marginTop':'-6px'}}>
<div>
<DropdownMenuContainer items={menu} icon='chevron-down' size={size} direction='right' />
</div>
</div>

View File

@@ -0,0 +1 @@
export { default } from './action_bar';

View File

@@ -1,37 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Avatar from '../../../components/avatar/avatar';
import DisplayName from '../../../components/display_name/display_name';
import { makeGetAccount } from '../../../selectors';
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, { id }) => ({
account: getAccount(state, id),
});
return mapStateToProps;
};
export default @connect(makeMapStateToProps)
class AutosuggestAccount extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
};
render () {
const { account } = this.props;
return (
<div className='autosuggest-account' title={account.get('acct')}>
<div className='autosuggest-account-icon'>
<Avatar account={account} size={18} />
</div>
<DisplayName account={account} />
</div>
);
}
}

View File

@@ -1,4 +1,7 @@
import { length } from 'stringz';
import classNames from 'classnames';
import './character_counter.scss';
export default class CharacterCounter extends PureComponent {
@@ -7,17 +10,16 @@ export default class CharacterCounter extends PureComponent {
max: PropTypes.number.isRequired,
};
checkRemainingText (diff) {
if (diff < 0) {
return <span className='character-counter character-counter--over'>{diff}</span>;
}
return <span className='character-counter'>{diff}</span>;
}
render () {
const diff = this.props.max - length(this.props.text);
return this.checkRemainingText(diff);
const classes = classNames('character-counter', {
'character-counter--over': (diff < 0),
});
<div className='character-counter__wrapper'>
<span className={classes}>{diff}</span>
</div>
}
}

View File

@@ -0,0 +1,16 @@
.character-counter {
cursor: default;
font-family: $font-sans-serif, sans-serif;
color: $gab-secondary-text;
@include text-sizing(14px, 600);
&--over {
color: $warning-red;
}
&__wrapper {
align-self: center;
margin-right: 4px;
}
}

View File

@@ -0,0 +1 @@
export { default } from './character_counter';

View File

@@ -3,21 +3,23 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import ImmutablePropTypes from 'react-immutable-proptypes';
import classNames from 'classnames';
import CharacterCounter from './character_counter';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextbox from '../../../components/autosuggest_textbox';
import PollButtonContainer from '../containers/poll_button_container';
import UploadButtonContainer from '../containers/upload_button_container';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../containers/poll_form_container';
import UploadForm from './upload_form';
import WarningContainer from '../containers/warning_container';
import { isMobile } from '../../../utils/is_mobile';
import { countableText } from '../util/counter';
import Icon from '../../../components/icon';
import Button from '../../../components/button';
import CharacterCounter from '../character_counter';
import ReplyIndicatorContainer from '../../containers/reply_indicator_container';
import AutosuggestTextbox from '../../../../components/autosuggest_textbox';
import PollButtonContainer from '../../containers/poll_button_container';
import UploadButtonContainer from '../../containers/upload_button_container';
import SpoilerButtonContainer from '../../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../../containers/privacy_dropdown_container';
import EmojiPickerDropdown from '../../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../../containers/poll_form_container';
import UploadForm from '../upload_form/upload_form';
import WarningContainer from '../../containers/warning_container';
import { isMobile } from '../../../../utils/is_mobile';
import { countableText } from '../../util/counter';
import Icon from '../../../../components/icon';
import Button from '../../../../components/button';
import './compose_form.scss';
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
const maxPostCharacterCount = 3000;
@@ -278,7 +280,7 @@ class ComposeForm extends ImmutablePureComponent {
<PrivacyDropdownContainer />
<SpoilerButtonContainer />
</div>
<div className='character-counter__wrapper'><CharacterCounter max={maxPostCharacterCount} text={text} /></div>
<CharacterCounter max={maxPostCharacterCount} text={text} />
</div>
}

View File

@@ -0,0 +1 @@
export { default } from './compose_form';

View File

@@ -1,10 +1,12 @@
import { defineMessages, injectIntl } from 'react-intl';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
import Overlay from 'react-overlays/lib/Overlay';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import detectPassiveEvents from 'detect-passive-events';
import { buildCustomEmojis } from '../../../components/emoji/emoji';
import Overlay from 'react-overlays/lib/Overlay';
import { EmojiPicker as EmojiPickerAsync } from '../../../ui/util/async-components';
import { buildCustomEmojis } from '../../../../components/emoji/emoji';
import './emoji_picker_dropdown.scss';
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
@@ -91,12 +93,24 @@ class ModifierPickerMenu extends PureComponent {
return (
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
<button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
<button onClick={this.handleClick} data-index={1}>
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} />
</button>
<button onClick={this.handleClick} data-index={2}>
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} />
</button>
<button onClick={this.handleClick} data-index={3}>
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} />
</button>
<button onClick={this.handleClick} data-index={4}>
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} />
</button>
<button onClick={this.handleClick} data-index={5}>
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} />
</button>
<button onClick={this.handleClick} data-index={6}>
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} />
</button>
</div>
);
}

View File

@@ -0,0 +1,86 @@
.emoji-picker-dropdown__menu {
background: $simple-background-color;
position: absolute;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
border-radius: 4px;
margin-top: 5px;
z-index: 20000;
.emoji-mart-scroll {
transition: opacity 200ms ease;
}
&.selecting .emoji-mart-scroll {
opacity: 0.5;
}
}
.emoji-picker-dropdown__modifiers {
position: absolute;
top: 60px;
right: 11px;
cursor: pointer;
}
.emoji-picker-dropdown__modifiers__menu {
position: absolute;
z-index: 4;
top: -4px;
left: -8px;
background: $simple-background-color;
border-radius: 4px;
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
overflow: hidden;
button {
display: block;
cursor: pointer;
border: 0;
padding: 4px 8px;
background: transparent;
&:hover,
&:focus,
&:active {
background: rgba($ui-secondary-color, 0.4);
}
}
.emoji-mart-emoji {
height: 22px;
}
}
.emoji-button {
display: block;
font-size: 24px;
line-height: 24px;
margin-left: 2px;
width: 24px;
outline: 0;
cursor: pointer;
&:active,
&:focus {
outline: 0 !important;
}
img {
filter: grayscale(100%);
opacity: 0.8;
display: block;
margin: 0;
width: 22px;
height: 22px;
margin-top: 2px;
}
&:hover,
&:active,
&:focus {
img {
opacity: 1;
filter: none;
}
}
}

View File

@@ -0,0 +1 @@
export { default } from './emoji_picker_dropdown';

View File

@@ -0,0 +1 @@
export { default } from './navigation_bar';

View File

@@ -1,11 +1,13 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ActionBar from './action_bar';
import Avatar from '../../../components/avatar';
import Permalink from '../../../components/permalink';
import IconButton from '../../../components/icon_button';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { me } from '../../../initial_state';
import { FormattedMessage } from 'react-intl';
import ActionBar from '../action_bar';
import Avatar from '../../../../components/avatar';
import Permalink from '../../../../components/permalink';
import IconButton from '../../../../components/icon_button';
import { me } from '../../../../initial_state';
import './navigation_bar.scss';
const mapStateToProps = state => {
return {
@@ -22,16 +24,18 @@ class NavigationBar extends ImmutablePureComponent {
};
render () {
const { account } = this.props;
return (
<div className='navigation-bar'>
<Permalink href={this.props.account.get('url')} to={`/${this.props.account.get('acct')}`}>
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
<Avatar account={this.props.account} size={48} />
<Permalink href={account.get('url')} to={`/${account.get('acct')}`}>
<span style={{ display: 'none' }}>{account.get('acct')}</span>
<Avatar account={account} size={48} />
</Permalink>
<div className='navigation-bar__profile'>
<Permalink href={this.props.account.get('url')} to={`/${this.props.account.get('acct')}`}>
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
<Permalink href={account.get('url')} to={`/${account.get('acct')}`}>
<strong className='navigation-bar__profile-account'>@{account.get('acct')}</strong>
</Permalink>
<a href='/settings/profile' className='navigation-bar__profile-edit'>
@@ -41,7 +45,7 @@ class NavigationBar extends ImmutablePureComponent {
<div className='navigation-bar__actions'>
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} />
<ActionBar account={this.props.account} />
<ActionBar account={account} />
</div>
</div>
);

View File

@@ -0,0 +1,89 @@
.navigation-bar {
padding: 10px;
display: flex;
align-items: center;
flex-shrink: 0;
cursor: default;
color: $darker-text-color;
strong {
color: $secondary-text-color;
}
a {
color: inherit;
}
.permalink {
text-decoration: none;
}
&__actions {
position: relative;
.icon-button.close {
position: absolute;
pointer-events: none;
transform: scale(0.0, 1.0) translate(-100%, 0);
opacity: 0;
}
.compose__action-bar .icon-button {
pointer-events: auto;
transform: scale(1.0, 1.0) translate(0, 0);
opacity: 1;
}
}
&__profile {
flex: 1 1 auto;
margin-left: 8px;
line-height: 20px;
margin-top: -1px;
overflow: hidden;
}
&__profile-account {
display: block;
font-weight: 500;
@include text-overflow;
}
&__profile-edit {
color: inherit;
text-decoration: none;
}
@media screen and (max-width: 630px) and (max-height: 400px) {
$duration: 400ms;
$delay: 100ms;
will-change: padding-bottom;
transition: padding-bottom $duration $delay;
&>a:first-child {
will-change: margin-top, margin-left, margin-right, width;
transition: margin-top $duration $delay, margin-left $duration ($duration + $delay), margin-right $duration ($duration + $delay);
}
&__profile-edit {
will-change: margin-top;
transition: margin-top $duration $delay;
}
&__actions {
>.icon-button.close {
will-change: opacity transform;
transition: opacity $duration * 0.5 $delay,
transform $duration $delay;
}
.compose__action-bar .icon-button {
will-change: opacity transform;
transition: opacity $duration * 0.5 $delay + $duration * 0.5,
transform $duration $delay;
}
}
}
}

View File

@@ -0,0 +1 @@
export { default } from './poll_button';

View File

@@ -1,4 +1,4 @@
import IconButton from '../../../components/icon_button';
import IconButton from '../../../../components/icon_button';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
@@ -35,13 +35,13 @@ class PollButton extends PureComponent {
}
return (
<div className='compose-form__poll-button'>
<div>
<IconButton
icon='tasks'
title={intl.formatMessage(active ? messages.remove_poll : messages.add_poll)}
disabled={disabled}
onClick={this.handleClick}
className={`compose-form__poll-button-icon ${active ? 'active' : ''}`}
className={`${active ? 'active' : ''}`}
size={18}
inverted
style={iconStyle}

View File

@@ -0,0 +1 @@
export { default } from './poll_form';

View File

@@ -2,9 +2,11 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button';
import Icon from '../../../components/icon';
import AutosuggestTextbox from '../../../components/autosuggest_textbox';
import IconButton from '../../../../components/icon_button';
import Icon from '../../../../components/icon';
import AutosuggestTextbox from '../../../../components/autosuggest_textbox';
import './poll_form.scss';
const messages = defineMessages({
option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' },

View File

@@ -0,0 +1,10 @@
.poll-list-item {
display: flex;
align-items: center;
&__text {
flex: 0 0 auto;
width: calc(100% - (23px + 6px));
margin-right: 6px;
}
}

View File

@@ -0,0 +1 @@
export { default } from './privacy_dropdown';

View File

@@ -3,9 +3,11 @@ import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events';
import classNames from 'classnames';
import Overlay from 'react-overlays/lib/Overlay';
import IconButton from '../../../components/icon_button';
import Motion from '../../ui/util/optional_motion';
import Icon from '../../../components/icon';
import IconButton from '../../../../components/icon_button';
import Motion from '../../../ui/util/optional_motion';
import Icon from '../../../../components/icon';
import './privacy_dropdown.scss';
const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },

View File

@@ -0,0 +1,101 @@
.privacy-dropdown {
&__dropdown {
position: absolute;
background: $simple-background-color;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 4px;
margin-left: 40px;
overflow: hidden;
z-index: 10000;
&.top {
transform-origin: 50% 100%;
}
&.bottom {
transform-origin: 50% 0;
}
}
&__option {
color: $inverted-text-color;
padding: 10px;
cursor: pointer;
display: flex;
&:hover,
&.active {
background: $ui-highlight-color;
color: $primary-text-color;
outline: 0;
.privacy-dropdown__option__content {
color: $primary-text-color;
strong {
color: $primary-text-color;
}
}
}
&.active:hover {
background: lighten($ui-highlight-color, 4%);
}
&__icon {
margin-right: 10px;
@include flex(center, center);
}
&__content {
flex: 1 1 auto;
color: $lighter-text-color;
strong {
font-weight: 500;
display: block;
color: $inverted-text-color;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
}
}
&.active & {
&__dropdown {
display: block;
box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1);
}
&__value {
background: $simple-background-color;
border-radius: 4px 4px 0 0;
box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1);
.icon-button {
transition: none;
}
&.active {
background: $ui-highlight-color;
.icon-button {
color: $primary-text-color;
}
}
}
}
&.active.top & {
&__value {
border-radius: 0 0 4px 4px;
}
}
}

View File

@@ -0,0 +1 @@
export { default } from './reply_indicator';

View File

@@ -1,11 +1,13 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import Avatar from '../../../components/avatar';
import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { isRtl } from '../../../utils/rtl';
import { defineMessages, injectIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import Avatar from '../../../../components/avatar';
import IconButton from '../../../../components/icon_button';
import DisplayName from '../../../../components/display_name';
import { isRtl } from '../../../../utils/rtl';
import './reply_indicator.scss';
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
@@ -36,22 +38,26 @@ class ReplyIndicator extends ImmutablePureComponent {
}
const content = { __html: status.get('contentHtml') };
const style = {
const style = {
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
};
return (
<div className='reply-indicator'>
<div className='reply-indicator__header'>
<div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted /></div>
<div className='reply-indicator__cancel'>
<IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted />
</div>
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='reply-indicator__display-name'>
<div className='reply-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div>
<div className='reply-indicator__display-avatar'>
<Avatar account={status.get('account')} size={24} />
</div>
<DisplayName account={status.get('account')} />
</NavLink>
</div>
<div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
<div className='reply-indicator-content' style={style} dangerouslySetInnerHTML={content} />
</div>
);
}

View File

@@ -0,0 +1,140 @@
.reply-indicator {
border-radius: 4px;
margin-bottom: 10px;
background: $gab-background-base-light;
padding: 10px;
min-height: 23px;
overflow-y: auto;
flex: 0 2 auto;
max-height: 500px;
@media screen and (min-width: 320px) and (max-width: 375px) {
max-height: 220px;
}
@media screen and (max-width: 320px) {
max-height: 130px;
}
&__header {
margin-bottom: 5px;
overflow: hidden;
}
&__cancel {
float: right;
line-height: 24px;
}
&__display-name {
color: $inverted-text-color;
display: block;
max-width: 100%;
line-height: 24px;
overflow: hidden;
padding-right: 25px;
text-decoration: none;
&:hover strong {
text-decoration: underline;
}
}
&__display-avatar {
float: left;
margin-right: 5px;
}
}
.reply-indicator-content {
position: relative;
font-size: 15px;
line-height: 20px;
word-wrap: break-word;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 2px;
color: $primary-text-color;
&:focus {
outline: 0;
}
&.status__content--with-spoiler {
white-space: normal;
.status__content__text {
white-space: pre-wrap;
}
}
.emojione {
width: 20px;
height: 20px;
margin: -3px 0 0;
}
p {
margin-bottom: 20px;
white-space: pre-wrap;
&:last-child {
margin-bottom: 2px;
}
}
a {
color: $gab-brand-default;
text-decoration: none;
&:hover {
text-decoration: underline;
.fa {
color: lighten($dark-text-color, 7%);
}
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
.fa {
color: $dark-text-color;
}
}
.status__content__spoiler-link {
background: $action-button-color;
&:hover {
background: lighten($action-button-color, 7%);
text-decoration: none;
}
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus,
&:active {
outline: 0 !important;
}
}
.status__content__text {
display: none;
&.status__content__text--visible {
display: block;
}
}
}

View File

@@ -1,45 +1,12 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl } from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
import spring from 'react-motion/lib/spring';
import Motion from '../../ui/util/optional_motion';
import { searchEnabled } from '../../../initial_state';
import Icon from '../../../components/icon';
import SearchPopout from '../../../components/search_popout';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
});
class SearchPopout extends PureComponent {
static propTypes = {
style: PropTypes.object,
};
render () {
const { style } = this.props;
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favorited, reposted, 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 (
<div className='search-popout-container' style={{ ...style, position: 'absolute', zIndex: 1000 }}>
<Motion defaultStyle={{ opacity: 0, scaleX: 1, scaleY: 1 }} 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</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}
</div>
)}
</Motion>
</div>
);
}
}
export default @injectIntl
class Search extends PureComponent {

View File

@@ -0,0 +1 @@
export { default } from './search_results';

View File

@@ -1,18 +1,18 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import TrendingItem from '../../../components/trending_item';
import Icon from '../../../components/icon';
import WhoToFollowPanel from '../../../components/panel';
import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../../../containers/account_container';
import StatusContainer from '../../../../containers/status_container';
import TrendingItem from '../../../../components/trending_item';
import Icon from '../../../../components/icon';
import WhoToFollowPanel from '../../../../components/panel';
export default @injectIntl
class SearchResults extends ImmutablePureComponent {
import './search_results.scss';
export default class SearchResults extends ImmutablePureComponent {
static propTypes = {
results: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
};
state = {
@@ -20,14 +20,13 @@ class SearchResults extends ImmutablePureComponent {
}
render () {
const { intl, results, dismissSuggestion } = this.props;
const { results } = this.props;
const { isSmallScreen } = this.state;
if (results.isEmpty() && isSmallScreen) {
return (
<div className='search-results'>
<WhoToFollowPanel />
{/* <TrendsPanel /> */}
</div>
);
}
@@ -36,10 +35,13 @@ class SearchResults extends ImmutablePureComponent {
let count = 0;
if (results.get('accounts') && results.get('accounts').size > 0) {
count += results.get('accounts').size;
count += results.get('accounts').size;
accounts = (
<div className='search-results__section'>
<h5><Icon id='users' fixedWidth /><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
<h5>
<Icon id='users' fixedWidth />
<FormattedMessage id='search_results.accounts' defaultMessage='People' />
</h5>
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
</div>
@@ -47,10 +49,13 @@ class SearchResults extends ImmutablePureComponent {
}
if (results.get('statuses') && results.get('statuses').size > 0) {
count += results.get('statuses').size;
count += results.get('statuses').size;
statuses = (
<div className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Gabs' /></h5>
<h5>
<Icon id='quote-right' fixedWidth />
<FormattedMessage id='search_results.statuses' defaultMessage='Gabs' />
</h5>
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
</div>
@@ -72,7 +77,13 @@ class SearchResults extends ImmutablePureComponent {
<div className='search-results'>
<div className='search-results__header'>
<Icon id='search' fixedWidth />
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
<FormattedMessage
id='search_results.total'
defaultMessage='{count, number} {count, plural, one {result} other {results}}'
values={{
count
}}
/>
</div>
{accounts}

View File

@@ -0,0 +1,40 @@
.search-results {
&__header {
color: $dark-text-color;
background: lighten($ui-base-color, 2%);
padding: 15px;
cursor: default;
@include text-sizing(16px, 500);
.fa {
display: inline-block;
margin-right: 5px;
}
}
&__section {
margin-bottom: 5px;
h5 {
display: flex;
background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
color: $dark-text-color;
padding: 15px;
cursor: default;
@include text-sizing(16px, 500);
.fa {
display: inline-block;
margin-right: 5px;
}
}
.account:last-child,
&>div:last-child .status {
border-bottom: 0;
}
}
}

View File

@@ -0,0 +1 @@
export { default } from './text_icon_button';

View File

@@ -1,4 +1,3 @@
export default class TextIconButton extends PureComponent {
static propTypes = {
@@ -18,7 +17,14 @@ export default class TextIconButton extends PureComponent {
const { label, title, active, ariaControls } = this.props;
return (
<button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}>
<button
title={title}
aria-label={title}
className={`text-icon-button ${active ? 'text-icon-button--active' : ''}`}
aria-expanded={active}
onClick={this.handleClick}
aria-controls={ariaControls}
>
{label}
</button>
);

View File

@@ -0,0 +1,32 @@
.text-icon-button {
color: $gab-secondary-text;
border: none;
background: transparent;
cursor: pointer;
padding: 0 3px;
outline: 0;
transition: color 100ms ease-in;
@include text-sizing(11px, 600, 27px);
&:hover,
&:active,
&:focus {
color: darken($lighter-text-color, 7%);
transition: color 200ms ease-out;
}
&--active {
color: $highlight-text-color;
}
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus,
&:active {
outline: 0 !important;
}
}

View File

@@ -0,0 +1 @@
export { default } from './upload';

View File

@@ -1,13 +1,17 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames';
import spring from 'react-motion/lib/spring';
import Motion from '../../ui/util/optional_motion';
import Icon from '../../../components/icon';
import Motion from '../../../ui/util/optional_motion';
import IconButton from '../../../../components/icon_button';
import './upload.scss';
const messages = defineMessages({
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
undo: { id: 'upload_form.undo', defaultMessage: 'Delete' },
focus: { id: 'upload_form.focus', defaultMessage: 'Crop' },
});
export default @injectIntl
@@ -93,16 +97,30 @@ class Upload extends ImmutablePureComponent {
const y = ((focusY / -2) + .5) * 100;
return (
<div className='compose-form__upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
<div className='compose-form-upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) => (
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className={classNames('compose-form__upload__actions', { active })}>
<button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
<div className='compose-form-upload__thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className={classNames('compose-form-upload__actions', { active })}>
<IconButton
onClick={this.handleUndoClick}
icon='times'
title={intl.formatMessage(messages.undo)}
text={intl.formatMessage(messages.undo)}
/>
{
media.get('type') === 'image' &&
<IconButton
onClick={this.handleFocalPointClick}
icon='crosshairs'
title={intl.formatMessage(messages.focus)}
text={intl.formatMessage(messages.focus)}
/>
}
</div>
<div className={classNames('compose-form__upload-description', { active })}>
<div className={classNames('compose-form-upload__description', { active })}>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>

View File

@@ -0,0 +1,78 @@
.compose-form-upload {
flex: 1 1 0;
min-width: 40%;
margin: 5px;
&__actions {
background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
opacity: 0;
transition: opacity .1s ease;
@include flex(space-between, flex-start);
.icon-button {
flex: 0 1 auto;
color: $gab-secondary-text;
padding: 10px;
font-family: inherit;
@include text-sizing(14px, 500);
&:hover,
&:focus,
&:active {
color: $gab-text-highlight;
}
}
&.active {
opacity: 1;
}
}
&__description {
z-index: 2;
box-sizing: border-box;
background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
padding: 10px;
opacity: 0;
transition: opacity .1s ease;
@include abs-position(auto, 0, 0, 0);
textarea {
background: rgba(0, 0, 0, 0.3);
box-sizing: border-box;
background: transparent;
color: $gab-secondary-text;
border: 1px solid $gab-secondary-text;
outline: none;
padding: 10px;
margin: 0;
width: 100%;
font-family: inherit;
@include text-sizing(14px, 500);
&:focus {
color: #fff;
}
&::placeholder {
color: $gab-secondary-text;
}
}
&.active {
opacity: 1;
}
}
&__thumbnail {
border-radius: 4px;
overflow: hidden;
@include size(100%, 140px);
@include background-image;
}
}

View File

@@ -0,0 +1 @@
export { default } from './upload_button';

View File

@@ -1,7 +1,9 @@
import IconButton from '../../../components/icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../../../../components/icon_button';
import './upload_button.scss';
const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add media (JPEG, PNG, GIF, WebM, MP4, MOV)' },
@@ -56,8 +58,17 @@ class UploadButton extends ImmutablePureComponent {
}
return (
<div className='compose-form__upload-button'>
<IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
<div className='compose-form-upload-button'>
<IconButton
inverted
icon='camera'
title={intl.formatMessage(messages.upload)}
disabled={disabled}
onClick={this.handleClick}
className='compose-form-upload-button__icon'
size={18}
style={iconStyle}
/>
<label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span>
<input

View File

@@ -0,0 +1,5 @@
.compose-form-upload-button {
&__icon {
line-height: 27px;
}
}

View File

@@ -1,8 +1,10 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import UploadProgress from './upload_progress';
import UploadContainer from '../containers/upload_container';
import SensitiveButtonContainer from '../containers/sensitive_button_container';
import UploadProgress from '../upload_progress';
import UploadContainer from '../../containers/upload_container';
import SensitiveButtonContainer from '../../containers/sensitive_button_container';
import './upload_form.scss';
const mapStateToProps = state => ({
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
@@ -19,10 +21,10 @@ class UploadForm extends ImmutablePureComponent {
const { mediaIds } = this.props;
return (
<div className='compose-form__upload-wrapper'>
<div className='compose-form-upload-wrapper'>
<UploadProgress />
<div className='compose-form__uploads-wrapper'>
<div className='compose-form-uploads-wrapper'>
{mediaIds.map(id => (
<UploadContainer id={id} key={id} />
))}

View File

@@ -0,0 +1,10 @@
.compose-form-upload-wrapper {
overflow: hidden;
}
.compose-form-uploads-wrapper {
display: flex;
flex-direction: row;
padding: 5px;
flex-wrap: wrap;
}

View File

@@ -0,0 +1 @@
export { default } from './upload_progress';

View File

@@ -1,7 +1,9 @@
import { FormattedMessage } from 'react-intl';
import spring from 'react-motion/lib/spring';
import Motion from '../../ui/util/optional_motion';
import Icon from '../../../components/icon';
import Motion from '../../../ui/util/optional_motion';
import Icon from '../../../../components/icon';
import './upload_progress.scss'
const mapStateToProps = state => ({
active: state.getIn(['compose', 'is_uploading']),

View File

@@ -0,0 +1,39 @@
.upload-progress {
padding: 10px;
color: $lighter-text-color;
overflow: hidden;
display: flex;
.fa {
font-size: 34px;
margin-right: 10px;
}
span {
text-transform: uppercase;
display: block;
@include text-sizing(12px, 500);
}
&__message {
flex: 1 1 auto;
}
&__backdrop {
border-radius: 6px;
background: $ui-base-lighter-color;
position: relative;
margin-top: 5px;
@include size(100%, 6px);
}
&__tracker {
height: 6px;
background: $ui-highlight-color;
border-radius: 6px;
@include abs-position(0, auto, auto, 0);
}
}

View File

@@ -0,0 +1 @@
export { default } from './warning';

View File

@@ -1,6 +1,8 @@
import Motion from '../../ui/util/optional_motion';
import Motion from '../../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import './warning.scss';
export default class Warning extends PureComponent {
static propTypes = {
@@ -13,7 +15,7 @@ export default class Warning extends PureComponent {
return (
<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='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
<div className='compose-form-warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
{message}
</div>
)}

View File

@@ -0,0 +1,33 @@
.compose-form-warning {
color: $inverted-text-color;
margin-bottom: 10px;
background: $ui-primary-color;
box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3);
padding: 8px 10px;
border-radius: 4px;
@include text-sizing(13px, 400);
strong {
color: $inverted-text-color;
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
color: $lighter-text-color;
font-weight: 500;
text-decoration: underline;
&:hover,
&:active,
&:focus {
text-decoration: none;
}
}
}

View File

@@ -0,0 +1,104 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router-dom';
import { injectIntl, defineMessages } from 'react-intl';
import spring from 'react-motion/lib/spring';
import Motion from '../ui/util/optional_motion';
import ComposeFormContainer from './containers/compose_form_container';
import NavigationBar from './components/navigation_bar';
import { mountCompose, unmountCompose } from '../../actions/compose';
import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/logo_ui_column_footer.png';
import { mascot } from '../../initial_state';
import Icon from '../../components/icon';
import './compose.scss';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new gab' },
});
const mapStateToProps = (state, ownProps) => ({
columns: state.getIn(['settings', 'columns']),
showSearch: ownProps.isSearchPage,
});
export default @connect(mapStateToProps)
@injectIntl
class Compose extends PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
columns: ImmutablePropTypes.list.isRequired,
showSearch: PropTypes.bool,
isSearchPage: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
componentDidMount () {
const { isSearchPage } = this.props;
if (!isSearchPage) {
this.props.dispatch(mountCompose());
}
}
componentWillUnmount () {
const { isSearchPage } = this.props;
if (!isSearchPage) {
this.props.dispatch(unmountCompose());
}
}
onFocus = () => {
this.props.dispatch(changeComposing(true));
}
onBlur = () => {
this.props.dispatch(changeComposing(false));
}
render () {
const { showSearch, isSearchPage, intl } = this.props;
let header = '';
return (
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
{header}
{isSearchPage && <SearchContainer /> }
<div className='drawer__pager'>
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}>
<NavigationBar onClose={this.onBlur} />
<ComposeFormContainer />
<div className='drawer__inner__gabsocial'>
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
</div>
</div>}
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
<SearchResultsContainer />
</div>
)}
</Motion>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,107 @@
.drawer {
width: 300px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow-y: hidden;
}
.drawer__tab {
display: block;
flex: 1 1 auto;
padding: 15px 5px 13px;
color: $darker-text-color;
text-decoration: none;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
}
.drawer {
flex: 1 1 100%;
overflow: hidden;
}
.drawer__pager {
box-sizing: border-box;
padding: 0;
flex-grow: 1;
position: relative;
overflow: hidden;
display: flex;
}
.drawer__inner {
top: 0;
left: 0;
background: lighten($ui-base-color, 13%);
box-sizing: border-box;
padding: 0;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
width: 100%;
height: 100%;
&.darker {
background: $ui-base-color;
}
}
.drawer__inner__gabsocial {
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
flex: 1;
min-height: 47px;
display: none;
>img {
display: block;
object-fit: contain;
object-position: bottom left;
width: 100%;
height: 100%;
pointer-events: none;
user-drag: none;
user-select: none;
}
@media screen and (min-height: 640px) {
display: block;
}
}
.drawer__header {
flex: 0 0 auto;
font-size: 16px;
background: lighten($ui-base-color, 8%);
margin-bottom: 10px;
display: flex;
flex-direction: row;
a {
transition: background 100ms ease-in;
&:hover {
background: lighten($ui-base-color, 3%);
transition: background 200ms ease-out;
}
}
}
@media screen and (min-width: 631px) {
.drawer {
flex: 0 0 auto;
padding: 10px;
padding-left: 5px;
padding-right: 5px;
&:first-child {
padding-left: 10px;
}
&:last-child {
padding-right: 10px;
}
}
}

View File

@@ -1,126 +1 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router-dom';
import { injectIntl, defineMessages } from 'react-intl';
import spring from 'react-motion/lib/spring';
import Motion from '../ui/util/optional_motion';
import ComposeFormContainer from './containers/compose_form_container';
import NavigationBar from './components/navigation_bar';
import { mountCompose, unmountCompose } from '../../actions/compose';
import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/logo_ui_column_footer.png';
import { mascot } from '../../initial_state';
import Icon from '../../components/icon';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new gab' },
});
const mapStateToProps = (state, ownProps) => ({
columns: state.getIn(['settings', 'columns']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
});
export default @connect(mapStateToProps)
@injectIntl
class Compose extends PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
columns: ImmutablePropTypes.list.isRequired,
multiColumn: PropTypes.bool,
showSearch: PropTypes.bool,
isSearchPage: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
componentDidMount () {
const { isSearchPage } = this.props;
if (!isSearchPage) {
this.props.dispatch(mountCompose());
}
}
componentWillUnmount () {
const { isSearchPage } = this.props;
if (!isSearchPage) {
this.props.dispatch(unmountCompose());
}
}
onFocus = () => {
this.props.dispatch(changeComposing(true));
}
onBlur = () => {
this.props.dispatch(changeComposing(false));
}
render () {
const { multiColumn, showSearch, isSearchPage, intl } = this.props;
let header = '';
if (multiColumn) {
const { columns } = this.props;
header = (
<nav className='drawer__header'>
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
{!columns.some(column => column.get('id') === 'HOME') && (
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'PUBLIC') && (
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
)}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><Icon id='sign-out' fixedWidth /></a>
</nav>
);
}
return (
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
{header}
{(multiColumn || isSearchPage) && <SearchContainer /> }
<div className='drawer__pager'>
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}>
<NavigationBar onClose={this.onBlur} />
<ComposeFormContainer />
<div className='drawer__inner__gabsocial'>
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
</div>
</div>}
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
<SearchResultsContainer />
</div>
)}
</Motion>
</div>
</div>
);
}
}
export { default } from './compose';