@@ -79,6 +79,7 @@ class AutosuggestTextarea extends ImmutablePureComponent { | |||
} | |||
// auto-resize textarea | |||
e.target.style.height = 'auto'; | |||
e.target.style.height = `${e.target.scrollHeight}px`; | |||
this.props.onChange(e); | |||
@@ -197,7 +198,7 @@ class AutosuggestTextarea extends ImmutablePureComponent { | |||
style={style} | |||
/> | |||
<div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'> | |||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}> | |||
{suggestions.map((suggestion, i) => ( | |||
<div | |||
role='button' | |||
@@ -26,15 +26,19 @@ class Button extends React.PureComponent { | |||
render () { | |||
const style = { | |||
display: this.props.block ? 'block' : 'inline-block', | |||
width: this.props.block ? '100%' : 'auto', | |||
padding: `0 ${this.props.size / 2.25}px`, | |||
height: `${this.props.size}px`, | |||
lineHeight: `${this.props.size}px` | |||
lineHeight: `${this.props.size}px`, | |||
...this.props.style | |||
}; | |||
return ( | |||
<button className={`button ${this.props.secondary ? 'button-secondary' : ''}`} disabled={this.props.disabled} onClick={this.handleClick} style={{ ...style, ...this.props.style }}> | |||
<button | |||
className={`button ${this.props.secondary ? 'button-secondary' : ''} ${this.props.block ? 'button--block' : ''}`} | |||
disabled={this.props.disabled} | |||
onClick={this.handleClick} | |||
style={style} | |||
> | |||
{this.props.text || this.props.children} | |||
</button> | |||
); | |||
@@ -35,7 +35,7 @@ class ColumnCollapsable extends React.PureComponent { | |||
<i className={`fa fa-${icon}`} /> | |||
</div> | |||
<div className='column-collapsable__content' style={{ height: `${fullHeight}px`, maxHeight: '70vh' }}> | |||
<div className='column-collapsable__content' style={{ height: `${fullHeight}px` }}> | |||
{children} | |||
</div> | |||
</div> | |||
@@ -36,18 +36,15 @@ class IconButton extends React.PureComponent { | |||
} | |||
render () { | |||
let style = { | |||
const style = { | |||
fontSize: `${this.props.size}px`, | |||
width: `${this.props.size * 1.28571429}px`, | |||
height: `${this.props.size * 1.28571429}px`, | |||
lineHeight: `${this.props.size}px`, | |||
...this.props.style | |||
...this.props.style, | |||
...(this.props.active ? this.props.activeStyle : {}) | |||
}; | |||
if (this.props.active) { | |||
style = { ...style, ...this.props.activeStyle }; | |||
} | |||
const classes = ['icon-button']; | |||
if (this.props.active) { | |||
@@ -173,7 +173,7 @@ class MediaGallery extends React.PureComponent { | |||
return ( | |||
<div className='media-gallery' style={{ height: `${this.props.height}px` }}> | |||
<div className='spoiler-button' style={{ display: !this.state.visible ? 'none' : 'block' }}> | |||
<div className={`spoiler-button ${this.state.visible ? 'spoiler-button--visible' : ''}`}> | |||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} /> | |||
</div> | |||
@@ -117,15 +117,15 @@ class StatusContent extends React.PureComponent { | |||
return ( | |||
<div className='status__content' ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> | |||
<p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} > | |||
<span dangerouslySetInnerHTML={spoilerContent} /> <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>{toggleText}</button> | |||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}> | |||
<span dangerouslySetInnerHTML={spoilerContent} /> | |||
{' '} | |||
<button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>{toggleText}</button> | |||
</p> | |||
{mentionsPlaceholder} | |||
<div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} /> | |||
<div className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} /> | |||
</div> | |||
); | |||
} else if (this.props.onClick) { | |||
@@ -133,7 +133,7 @@ class StatusContent extends React.PureComponent { | |||
<div | |||
ref={this.setRef} | |||
className='status__content' | |||
style={{ ...directionStyle }} | |||
style={directionStyle} | |||
onMouseDown={this.handleMouseDown} | |||
onMouseUp={this.handleMouseUp} | |||
dangerouslySetInnerHTML={content} | |||
@@ -144,7 +144,7 @@ class StatusContent extends React.PureComponent { | |||
<div | |||
ref={this.setRef} | |||
className='status__content status__content--no-action' | |||
style={{ ...directionStyle }} | |||
style={directionStyle} | |||
dangerouslySetInnerHTML={content} | |||
/> | |||
); | |||
@@ -113,7 +113,7 @@ class VideoPlayer extends React.PureComponent { | |||
const { media, intl, width, height, sensitive, autoplay } = this.props; | |||
let spoilerButton = ( | |||
<div className='status__video-player-spoiler' style={{ display: !this.state.visible ? 'none' : 'block' }} > | |||
<div className={`status__video-player-spoiler ${this.state.visible ? 'status__video-player-spoiler--visible' : ''}`}> | |||
<IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} /> | |||
</div> | |||
); | |||
@@ -156,7 +156,7 @@ class VideoPlayer extends React.PureComponent { | |||
if (this.state.preview && !autoplay) { | |||
return ( | |||
<div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center` }} onClick={this.handleOpen}> | |||
<div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> | |||
{spoilerButton} | |||
<div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> | |||
</div> | |||
@@ -96,19 +96,19 @@ class Header extends ImmutablePureComponent { | |||
} | |||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { | |||
info = <span className='account--follows-info' style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span> | |||
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span> | |||
} | |||
if (me !== account.get('id')) { | |||
if (account.getIn(['relationship', 'requested'])) { | |||
actionBtn = ( | |||
<div style={{ position: 'absolute', top: '10px', left: '20px' }}> | |||
<div className='account--action-button'> | |||
<IconButton size={26} disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} /> | |||
</div> | |||
); | |||
} else if (!account.getIn(['relationship', 'blocking'])) { | |||
actionBtn = ( | |||
<div style={{ position: 'absolute', top: '10px', left: '20px' }}> | |||
<div className='account--action-button'> | |||
<IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} /> | |||
</div> | |||
); | |||
@@ -124,12 +124,12 @@ class Header extends ImmutablePureComponent { | |||
return ( | |||
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> | |||
<div style={{ padding: '20px 10px' }}> | |||
<div> | |||
<Avatar account={account} autoPlayGif={this.props.autoPlayGif} /> | |||
<span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> | |||
<span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span> | |||
<div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} /> | |||
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> | |||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> | |||
<div className='account__header__content' dangerouslySetInnerHTML={content} /> | |||
{info} | |||
{actionBtn} | |||
@@ -22,20 +22,6 @@ const settings = { | |||
imagePathPNG: '/emoji/' | |||
}; | |||
const dropdownStyle = { | |||
position: 'absolute', | |||
right: '5px', | |||
top: '5px' | |||
}; | |||
const dropdownTriggerStyle = { | |||
display: 'block', | |||
fontSize: '24px', | |||
lineHeight: '24px', | |||
marginLeft: '2px', | |||
width: '24px' | |||
} | |||
let EmojiPicker; // load asynchronously | |||
class EmojiPickerDropdown extends React.PureComponent { | |||
@@ -118,8 +104,8 @@ class EmojiPickerDropdown extends React.PureComponent { | |||
const { active, loading } = this.state; | |||
return ( | |||
<Dropdown ref={this.setRef} style={dropdownStyle} onShow={this.onShowDropdown} onHide={this.onHideDropdown}> | |||
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}> | |||
<Dropdown ref={this.setRef} className='emoji-picker__dropdown' onShow={this.onShowDropdown} onHide={this.onHideDropdown}> | |||
<DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)}> | |||
<img draggable="false" | |||
className={`emojione ${active && loading ? "pulse-loading" : ''}`} | |||
alt="🙂" src="/emoji/1f602.svg" /> | |||
@@ -60,7 +60,7 @@ class GettingStarted extends ImmutablePureComponent { | |||
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> | |||
</div> | |||
<div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}> | |||
<div className='getting-started__footer scrollable optionally-scrollable'> | |||
<div className='static-content getting-started'> | |||
<p> | |||
<FormattedMessage | |||
@@ -133,7 +133,7 @@ class UI extends React.PureComponent { | |||
<Compose withHeader={true} /> | |||
<HomeTimeline shouldUpdateScroll={noOp} /> | |||
<Notifications shouldUpdateScroll={noOp} /> | |||
<div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div> | |||
<div className="column__wrapper">{children}</div> | |||
</ColumnsArea> | |||
); | |||
} | |||
@@ -28,6 +28,7 @@ | |||
text-overflow: ellipsis; | |||
transition: all 100ms ease-in; | |||
white-space: nowrap; | |||
width: auto; | |||
&:active, | |||
&:focus, | |||
@@ -44,6 +45,17 @@ | |||
&.button-secondary { | |||
// | |||
} | |||
&.button--block { | |||
display: block; | |||
width: 100%; | |||
} | |||
} | |||
.column__wrapper { | |||
display: flex; | |||
flex: 1 1 auto; | |||
position: relative; | |||
} | |||
.column-collapsable { | |||
@@ -53,6 +65,7 @@ | |||
overflow: auto; | |||
transition: 300ms ease; | |||
opacity: 1; | |||
max-height: 70vh; | |||
} | |||
&.collapsed .column-collapsable__content { | |||
@@ -370,10 +383,16 @@ | |||
.compose-form__autosuggest-wrapper { | |||
position: relative; | |||
.dropdown--active::after { | |||
border-color: transparent transparent $base-border-color; | |||
bottom: -1px; | |||
right: 8px; | |||
.emoji-picker__dropdown { | |||
position: absolute; | |||
right: 5px; | |||
top: 5px; | |||
&.dropdown--active::after { | |||
border-color: transparent transparent $base-border-color; | |||
bottom: -1px; | |||
right: 8px; | |||
} | |||
} | |||
} | |||
@@ -498,6 +517,14 @@ | |||
text-decoration: none; | |||
} | |||
} | |||
.status__content__text { | |||
display: none; | |||
&.status__content__text--visible { | |||
display: block; | |||
} | |||
} | |||
} | |||
.status__content__spoiler-link { | |||
@@ -800,6 +827,7 @@ | |||
& > div { | |||
background: rgba(lighten($ui-base-color, 4%), 0.9); | |||
padding: 20px 10px; | |||
} | |||
.account__header__content { | |||
@@ -808,10 +836,18 @@ | |||
.account__header__display-name { | |||
color: $primary-text-color; | |||
display: inline-block; | |||
font-size: 20px; | |||
line-height: 27px; | |||
font-weight: 500; | |||
} | |||
.account__header__username { | |||
color: $ui-highlight-color; | |||
font-size: 14px; | |||
font-weight: 400; | |||
display: block; | |||
margin-bottom: 10px; | |||
} | |||
} | |||
@@ -1716,6 +1752,7 @@ | |||
} | |||
.autosuggest-textarea__suggestions { | |||
display: none; | |||
position: absolute; | |||
top: 100%; | |||
width: 100%; | |||
@@ -1724,6 +1761,10 @@ | |||
background: $ui-secondary-color; | |||
color: $ui-base-color; | |||
font-size: 14px; | |||
&.autosuggest-textarea__suggestions--visible { | |||
display: block; | |||
} | |||
} | |||
.autosuggest-textarea__suggestions__item { | |||
@@ -1778,6 +1819,11 @@ | |||
position: relative; | |||
} | |||
.getting-started__footer { | |||
display: flex; | |||
flex-direction: column; | |||
} | |||
.getting-started { | |||
box-sizing: border-box; | |||
padding-bottom: 235px; | |||
@@ -2051,11 +2097,16 @@ button.icon-button.active i.fa-retweet { | |||
} | |||
.spoiler-button { | |||
display: none; | |||
left: 4px; | |||
position: absolute; | |||
text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color; | |||
top: 4px; | |||
z-index: 100; | |||
&.spoiler-button--visible { | |||
display: block; | |||
} | |||
} | |||
.modal-container--preloader { | |||
@@ -2114,6 +2165,24 @@ button.icon-button.active i.fa-retweet { | |||
.account--follows-info { | |||
color: $primary-text-color; | |||
position: absolute; | |||
top: 10px; | |||
right: 10px; | |||
opacity: 0.7; | |||
display: inline-block; | |||
vertical-align: top; | |||
background-color: rgba($base-overlay-background, 0.4); | |||
text-transform: uppercase; | |||
font-size: 11px; | |||
font-weight: 500; | |||
padding: 4px; | |||
border-radius: 4px; | |||
} | |||
.account--action-button { | |||
position: absolute; | |||
top: 10px; | |||
left: 20px; | |||
} | |||
.setting-toggle__label { | |||
@@ -2531,6 +2600,11 @@ button.icon-button.active i.fa-retweet { | |||
} | |||
.emoji-button { | |||
display: block; | |||
font-size: 24px; | |||
line-height: 24px; | |||
margin-left: 2px; | |||
width: 24px; | |||
outline: 0; | |||
&:active, | |||
@@ -3345,12 +3419,17 @@ button.icon-button.active i.fa-retweet { | |||
} | |||
.status__video-player-spoiler { | |||
display: none; | |||
color: $primary-text-color; | |||
left: 4px; | |||
position: absolute; | |||
text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color; | |||
top: 4px; | |||
z-index: 100; | |||
&.status__video-player-spoiler--visible { | |||
display: block; | |||
} | |||
} | |||
.status__video-player-expand { | |||
@@ -3365,6 +3444,8 @@ button.icon-button.active i.fa-retweet { | |||
.media-spoiler-video { | |||
background-size: cover; | |||
background-repeat: no-repeat; | |||
background-position: center; | |||
cursor: pointer; | |||
margin-top: 8px; | |||
position: relative; | |||
@@ -51,14 +51,12 @@ describe('<Button />', () => { | |||
it('renders style="display: block; width: 100%;" if props.block given', () => { | |||
const wrapper = shallow(<Button block />); | |||
expect(wrapper.find('button')).to.have.style('display', 'block'); | |||
expect(wrapper.find('button')).to.have.style('width', '100%'); | |||
expect(wrapper.find('button')).to.have.className('button--block'); | |||
}); | |||
it('renders style="display: inline-block; width: auto;" by default', () => { | |||
const wrapper = shallow(<Button />); | |||
expect(wrapper.find('button')).to.have.style('display', 'inline-block'); | |||
expect(wrapper.find('button')).to.have.style('width', 'auto'); | |||
expect(wrapper.find('button')).to.not.have.className('button--block'); | |||
}); | |||
it('adds class "button-secondary" if props.secondary given', () => { | |||