diff options
author | reverite <samantha@chalker.io> | 2019-03-15 05:54:30 -0700 |
---|---|---|
committer | reverite <samantha@chalker.io> | 2019-03-15 05:54:30 -0700 |
commit | 75eeb003b09c53d3b4e98046d1c20b0ad8a887bb (patch) | |
tree | d2ae669ee583c613a474f8698b7ea718da803819 /app/javascript | |
parent | 41d1369391d70a9cd25bdf96cfe567975793ef5a (diff) | |
parent | c2fa0f7c40bcc4064e8baaa221665eadd391c001 (diff) |
Merge remote-tracking branch 'glitch/master' into production
Diffstat (limited to 'app/javascript')
49 files changed, 894 insertions, 1560 deletions
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index f89b4cb36..dd4f5fd44 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -106,7 +106,7 @@ const excludeTypesFromSettings = state => state.getIn(['settings', 'notification const excludeTypesFromFilter = filter => { - const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']); + const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']); return allTypes.filterNot(item => item === filter).toJS(); }; diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 1fa25ee4e..6be2b4700 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -238,7 +238,7 @@ export default class MediaGallery extends React.PureComponent { }; componentWillReceiveProps (nextProps) { - if (!is(nextProps.media, this.props.media)) { + if (!is(nextProps.media, this.props.media) || nextProps.revealed === true) { this.setState({ visible: nextProps.revealed === undefined ? (displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all') : nextProps.revealed }); } } diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 31f4f1ddd..c8bf75f79 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -466,6 +466,7 @@ export default class Status extends ImmutablePureComponent { onOpenVideo={this.handleOpenVideo} width={this.props.cachedMediaWidth} cacheWidth={this.props.cacheMediaWidth} + revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} />)} </Bundle> ); @@ -483,6 +484,7 @@ export default class Status extends ImmutablePureComponent { onOpenMedia={this.props.onOpenMedia} cacheWidth={this.props.cacheMediaWidth} defaultWidth={this.props.cachedMediaWidth} + revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} /> )} </Bundle> diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js index 4e329f546..481e6644e 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.js +++ b/app/javascript/flavours/glitch/components/status_prepend.js @@ -62,6 +62,13 @@ export default class StatusPrepend extends React.PureComponent { values={{ name : link }} /> ); + case 'poll': + return ( + <FormattedMessage + id='notification.poll' + defaultMessage='A poll you have voted in has ended' + /> + ); } return null; } @@ -75,7 +82,7 @@ export default class StatusPrepend extends React.PureComponent { <div className={type === 'reblogged_by' || type === 'featured' ? 'status__prepend-icon-wrapper' : 'notification__favourite-icon-wrapper'}> <i className={`fa fa-fw fa-${ - type === 'favourite' ? 'star star-icon' : (type === 'featured' ? 'thumb-tack' : 'retweet') + type === 'favourite' ? 'star star-icon' : (type === 'featured' ? 'thumb-tack' : (type === 'poll' ? 'tasks' : 'retweet')) } status__prepend-icon`} /> </div> diff --git a/app/javascript/flavours/glitch/containers/timeline_container.js b/app/javascript/flavours/glitch/containers/timeline_container.js index 5a1f41f7a..6ce556664 100644 --- a/app/javascript/flavours/glitch/containers/timeline_container.js +++ b/app/javascript/flavours/glitch/containers/timeline_container.js @@ -7,7 +7,6 @@ import { hydrateStore } from 'flavours/glitch/actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from 'mastodon/locales'; import PublicTimeline from 'flavours/glitch/features/standalone/public_timeline'; -import CommunityTimeline from 'flavours/glitch/features/standalone/community_timeline'; import HashtagTimeline from 'flavours/glitch/features/standalone/hashtag_timeline'; import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container'; import initialState from 'flavours/glitch/util/initial_state'; @@ -26,24 +25,22 @@ export default class TimelineContainer extends React.PureComponent { static propTypes = { locale: PropTypes.string.isRequired, hashtag: PropTypes.string, - showPublicTimeline: PropTypes.bool.isRequired, + local: PropTypes.bool, }; static defaultProps = { - showPublicTimeline: initialState.settings.known_fediverse, + local: !initialState.settings.known_fediverse, }; render () { - const { locale, hashtag, showPublicTimeline } = this.props; + const { locale, hashtag, local } = this.props; let timeline; if (hashtag) { timeline = <HashtagTimeline hashtag={hashtag} />; - } else if (showPublicTimeline) { - timeline = <PublicTimeline />; } else { - timeline = <CommunityTimeline />; + timeline = <PublicTimeline local={local} />; } return ( @@ -51,6 +48,7 @@ export default class TimelineContainer extends React.PureComponent { <Provider store={store}> <Fragment> {timeline} + {ReactDOM.createPortal( <ModalContainer />, document.getElementById('modal-container'), diff --git a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js b/app/javascript/flavours/glitch/features/account/components/profile_column_header.js index 1a6abef37..b6d373a2c 100644 --- a/app/javascript/flavours/glitch/features/account/components/profile_column_header.js +++ b/app/javascript/flavours/glitch/features/account/components/profile_column_header.js @@ -11,16 +11,18 @@ export default @injectIntl class ProfileColumnHeader extends React.PureComponent { static propTypes = { + onClick: PropTypes.func, intl: PropTypes.object.isRequired, }; render() { - const { intl } = this.props; + const { onClick, intl } = this.props; return ( <ColumnHeader icon='user-circle' title={intl.formatMessage(messages.profile)} + onClick={onClick} showBackButton /> ); diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js index a9ea5088e..63c1b2d86 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.js +++ b/app/javascript/flavours/glitch/features/account_gallery/index.js @@ -65,6 +65,10 @@ export default class AccountGallery extends ImmutablePureComponent { } } + handleHeaderClick = () => { + this.column.scrollTop(); + } + handleScrollToBottom = () => { if (this.props.hasMore) { this.handleLoadMore(this.props.medias.size > 0 ? this.props.medias.last().getIn(['status', 'id']) : undefined); @@ -94,6 +98,10 @@ export default class AccountGallery extends ImmutablePureComponent { return !(location.state && location.state.mastodonModalOpen); } + setRef = c => { + this.column = c; + } + render () { const { medias, isLoading, hasMore } = this.props; @@ -112,8 +120,8 @@ export default class AccountGallery extends ImmutablePureComponent { } return ( - <Column> - <ProfileColumnHeader /> + <Column ref={this.setRef}> + <ProfileColumnHeader onClick={this.handleHeaderClick} /> <ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={this.shouldUpdateScroll}> <div className='scrollable scrollable--flex' onScroll={this.handleScroll}> diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 415e3be20..d683b8789 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -57,10 +57,18 @@ export default class AccountTimeline extends ImmutablePureComponent { } } + handleHeaderClick = () => { + this.column.scrollTop(); + } + handleLoadMore = maxId => { this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); } + setRef = c => { + this.column = c; + } + render () { const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props; @@ -73,8 +81,8 @@ export default class AccountTimeline extends ImmutablePureComponent { } return ( - <Column name='account'> - <ProfileColumnHeader /> + <Column ref={this.setRef} name='account'> + <ProfileColumnHeader onClick={this.handleHeaderClick} /> <StatusList prepend={<HeaderContainer accountId={this.props.params.accountId} />} diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 124004cb6..6bb9f60fd 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -43,6 +43,10 @@ export default class Followers extends ImmutablePureComponent { } } + handleHeaderClick = () => { + this.column.scrollTop(); + } + handleScroll = (e) => { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -61,6 +65,10 @@ export default class Followers extends ImmutablePureComponent { return !(location.state && location.state.mastodonModalOpen); } + setRef = c => { + this.column = c; + } + render () { const { accountIds, hasMore } = this.props; @@ -79,8 +87,8 @@ export default class Followers extends ImmutablePureComponent { } return ( - <Column> - <ProfileColumnHeader /> + <Column ref={this.setRef}> + <ProfileColumnHeader onClick={this.handleHeaderClick} /> <ScrollContainer scrollKey='followers' shouldUpdateScroll={this.shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index 656100dad..3f2f091a1 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -43,6 +43,10 @@ export default class Following extends ImmutablePureComponent { } } + handleHeaderClick = () => { + this.column.scrollTop(); + } + handleScroll = (e) => { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -61,6 +65,10 @@ export default class Following extends ImmutablePureComponent { return !(location.state && location.state.mastodonModalOpen); } + setRef = c => { + this.column = c; + } + render () { const { accountIds, hasMore } = this.props; @@ -79,8 +87,8 @@ export default class Following extends ImmutablePureComponent { } return ( - <Column> - <ProfileColumnHeader /> + <Column ref={this.setRef}> + <ProfileColumnHeader onClick={this.handleHeaderClick} /> <ScrollContainer scrollKey='following' shouldUpdateScroll={this.shouldUpdateScroll}> <div className='scrollable' onScroll={this.handleScroll}> diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js index 4535d9849..bc4ad359c 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js @@ -328,6 +328,14 @@ export default class LocalSettingsPage extends React.PureComponent { > <FormattedMessage id='settings.inline_preview_cards' defaultMessage='Inline preview cards for external links' /> </LocalSettingsPageItem> + <LocalSettingsPageItem + settings={settings} + item={['media', 'reveal_behind_cw']} + id='mastodon-settings--reveal-behind-cw' + onChange={onChange} + > + <FormattedMessage id='settings.media_reveal_behind_cw' defaultMessage='Reveal sensitive media behind a CW by default' /> + </LocalSettingsPageItem> </div> ), ]; diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js index 4e35d5b4e..e29bd61f5 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js @@ -90,6 +90,17 @@ export default class ColumnSettings extends React.PureComponent { <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} /> </div> </div> + + <div role='group' aria-labelledby='notifications-poll'> + <span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span> + + <div className='column-settings__row'> + <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} /> + </div> + </div> </div> ); } diff --git a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js index f95a2c9de..3457b7598 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js +++ b/app/javascript/flavours/glitch/features/notifications/components/filter_bar.js @@ -6,6 +6,7 @@ const tooltips = defineMessages({ mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' }, favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' }, boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' }, + polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' }, follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, }); @@ -79,6 +80,13 @@ class FilterBar extends React.PureComponent { <i className='fa fa-fw fa-retweet' /> </button> <button + className={selectedFilter === 'poll' ? 'active' : ''} + onClick={this.onClick('poll')} + title={intl.formatMessage(tooltips.polls)} + > + <i className='fa fa-fw fa-tasks' /> + </button> + <button className={selectedFilter === 'follow' ? 'active' : ''} onClick={this.onClick('follow')} title={intl.formatMessage(tooltips.follows)} diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js index daafe3507..5c5bbf604 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.js +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js @@ -108,6 +108,27 @@ export default class Notification extends ImmutablePureComponent { withDismiss /> ); + case 'poll': + return ( + <StatusContainer + containerId={notification.get('id')} + hidden={hidden} + id={notification.get('status')} + account={notification.get('account')} + prepend='poll' + muted + notification={notification} + onMoveDown={onMoveDown} + onMoveUp={onMoveUp} + onMention={onMention} + getScrollPosition={getScrollPosition} + updateScrollBottom={updateScrollBottom} + cachedMediaWidth={this.props.cachedMediaWidth} + cacheMediaWidth={this.props.cacheMediaWidth} + onUnmount={this.props.onUnmount} + withDismiss + /> + ); default: return null; } diff --git a/app/javascript/flavours/glitch/features/standalone/community_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/community_timeline/index.js deleted file mode 100644 index 2b67e836a..000000000 --- a/app/javascript/flavours/glitch/features/standalone/community_timeline/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; -import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; -import Column from 'flavours/glitch/components/column'; -import ColumnHeader from 'flavours/glitch/components/column_header'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connectCommunityStream } from 'flavours/glitch/actions/streaming'; - -const messages = defineMessages({ - title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, -}); - -@connect() -@injectIntl -export default class CommunityTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - } - - setRef = c => { - this.column = c; - } - - componentDidMount () { - const { dispatch } = this.props; - - dispatch(expandCommunityTimeline()); - this.disconnect = dispatch(connectCommunityStream()); - } - - componentWillUnmount () { - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } - - handleLoadMore = maxId => { - this.props.dispatch(expandCommunityTimeline({ maxId })); - } - - render () { - const { intl } = this.props; - - return ( - <Column ref={this.setRef} label={intl.formatMessage(messages.title)}> - <ColumnHeader - icon='users' - title={intl.formatMessage(messages.title)} - onClick={this.handleHeaderClick} - /> - - <StatusListContainer - timelineId='community' - onLoadMore={this.handleLoadMore} - scrollKey='standalone_public_timeline' - trackScroll={false} - /> - </Column> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js index 907da3992..5e2b3fc6d 100644 --- a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js +++ b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js @@ -1,70 +1,112 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; -import { expandPublicTimeline } from 'flavours/glitch/actions/timelines'; -import Column from 'flavours/glitch/components/column'; -import ColumnHeader from 'flavours/glitch/components/column_header'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connectPublicStream } from 'flavours/glitch/actions/streaming'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; +import { connectPublicStream, connectCommunityStream } from 'flavours/glitch/actions/streaming'; +import Masonry from 'react-masonry-infinite'; +import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; +import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container'; +import { debounce } from 'lodash'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; -const messages = defineMessages({ - title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, -}); +const mapStateToProps = (state, { local }) => { + const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap()); -@connect() -@injectIntl -export default class PublicTimeline extends React.PureComponent { + return { + statusIds: timeline.get('items', ImmutableList()), + isLoading: timeline.get('isLoading', false), + hasMore: timeline.get('hasMore', false), + }; +}; + +export default @connect(mapStateToProps) +class PublicTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + statusIds: ImmutablePropTypes.list.isRequired, + isLoading: PropTypes.bool.isRequired, + hasMore: PropTypes.bool.isRequired, + local: PropTypes.bool, }; - handleHeaderClick = () => { - this.column.scrollTop(); + componentDidMount () { + this._connect(); } - setRef = c => { - this.column = c; + componentDidUpdate (prevProps) { + if (prevProps.local !== this.props.local) { + this._disconnect(); + this._connect(); + } } - componentDidMount () { - const { dispatch } = this.props; - - dispatch(expandPublicTimeline()); - this.disconnect = dispatch(connectPublicStream()); + componentWillUnmount () { + this._disconnect(); } - componentWillUnmount () { + _connect () { + const { dispatch, local } = this.props; + + dispatch(local ? expandCommunityTimeline() : expandPublicTimeline()); + this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream()); + } + + _disconnect () { if (this.disconnect) { this.disconnect(); this.disconnect = null; } } - handleLoadMore = maxId => { - this.props.dispatch(expandPublicTimeline({ maxId })); + handleLoadMore = () => { + const { dispatch, statusIds, local } = this.props; + const maxId = statusIds.last(); + + if (maxId) { + dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId })); + } + } + + setRef = c => { + this.masonry = c; } + handleHeightChange = debounce(() => { + if (!this.masonry) { + return; + } + + this.masonry.forcePack(); + }, 50) + render () { - const { intl } = this.props; + const { statusIds, hasMore, isLoading } = this.props; + + const sizes = [ + { columns: 1, gutter: 0 }, + { mq: '415px', columns: 1, gutter: 10 }, + { mq: '640px', columns: 2, gutter: 10 }, + { mq: '960px', columns: 3, gutter: 10 }, + { mq: '1255px', columns: 3, gutter: 10 }, + ]; + + const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined; return ( - <Column ref={this.setRef} label={intl.formatMessage(messages.title)}> - <ColumnHeader - icon='globe' - title={intl.formatMessage(messages.title)} - onClick={this.handleHeaderClick} - /> - - <StatusListContainer - timelineId='public' - onLoadMore={this.handleLoadMore} - scrollKey='standalone_public_timeline' - trackScroll={false} - /> - </Column> + <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}> + {statusIds.map(statusId => ( + <div className='statuses-grid__item' key={statusId}> + <DetailedStatusContainer + id={statusId} + compact + measureHeight + onHeightChange={this.handleHeightChange} + /> + </div> + )).toArray()} + </Masonry> ); } diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index ad60320ef..e78f16c54 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -23,7 +23,7 @@ export default class DetailedStatus extends ImmutablePureComponent { }; static propTypes = { - status: ImmutablePropTypes.map.isRequired, + status: ImmutablePropTypes.map, settings: ImmutablePropTypes.map.isRequired, onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, @@ -138,6 +138,7 @@ export default class DetailedStatus extends ImmutablePureComponent { preventPlayback={!expanded} onOpenVideo={this.handleOpenVideo} autoplay + revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} /> ); mediaIcon = 'video-camera'; @@ -151,6 +152,7 @@ export default class DetailedStatus extends ImmutablePureComponent { fullwidth={settings.getIn(['media', 'fullwidth'])} hidden={!expanded} onOpenMedia={this.props.onOpenMedia} + revealed={settings.getIn(['media', 'reveal_behind_cw']) && !!status.get('spoiler_text') ? true : undefined} /> ); mediaIcon = 'picture-o'; diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 880372de5..73d3c7e9e 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -369,6 +369,10 @@ export default class Status extends ImmutablePureComponent { } } + handleHeaderClick = () => { + this.column.scrollTop(); + } + renderChildren (list) { return list.map(id => ( <StatusContainer @@ -390,6 +394,10 @@ export default class Status extends ImmutablePureComponent { this.node = c; } + setColumnRef = c => { + this.column = c; + } + componentDidUpdate (prevProps) { if (this.props.params.statusId && (this.props.params.statusId !== prevProps.params.statusId || prevProps.ancestorsIds.size < this.props.ancestorsIds.size)) { const { status, ancestorsIds } = this.props; @@ -452,10 +460,11 @@ export default class Status extends ImmutablePureComponent { }; return ( - <Column label={intl.formatMessage(messages.detailedStatus)}> + <Column ref={this.setColumnRef} label={intl.formatMessage(messages.detailedStatus)}> <ColumnHeader icon='comment' title={intl.formatMessage(messages.tootHeading)} + onClick={this.handleHeaderClick} showBackButton extraButton={( <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><i className={`fa fa-${!isExpanded ? 'eye-slash' : 'eye'}`} /></button> diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index cf66536c4..e3ed799c7 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -119,6 +119,12 @@ export default class Video extends React.PureComponent { revealed: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed, }; + componentWillReceiveProps (nextProps) { + if (nextProps.revealed === true) { + this.setState({ revealed: true }); + } + } + // hard coded in components.scss // any way to get ::before values programatically? volWidth = 50; diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index 93a404328..ef694d4ea 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -39,8 +39,9 @@ const initialState = ImmutableMap({ show_action_bar : true, }), media : ImmutableMap({ - letterbox : true, - fullwidth : true, + letterbox : true, + fullwidth : true, + reveal_behind_cw : false, }), notifications : ImmutableMap({ favicon_badge : false, diff --git a/app/javascript/flavours/glitch/reducers/push_notifications.js b/app/javascript/flavours/glitch/reducers/push_notifications.js index 1b47ca962..e87e8fc1a 100644 --- a/app/javascript/flavours/glitch/reducers/push_notifications.js +++ b/app/javascript/flavours/glitch/reducers/push_notifications.js @@ -9,6 +9,7 @@ const initialState = Immutable.Map({ favourite: false, reblog: false, mention: false, + poll: false, }), isSubscribed: false, browserSupport: false, diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index 384ff2d44..cc86a6b20 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -33,6 +33,7 @@ const initialState = ImmutableMap({ favourite: true, reblog: true, mention: true, + poll: true, }), quickFilter: ImmutableMap({ @@ -46,6 +47,7 @@ const initialState = ImmutableMap({ favourite: true, reblog: true, mention: true, + poll: true, }), sounds: ImmutableMap({ @@ -53,6 +55,7 @@ const initialState = ImmutableMap({ favourite: true, reblog: true, mention: true, + poll: true, }), }), diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss index 329482458..7a457600e 100644 --- a/app/javascript/flavours/glitch/styles/about.scss +++ b/app/javascript/flavours/glitch/styles/about.scss @@ -193,6 +193,7 @@ $small-breakpoint: 960px; } strong { + font-family: $font-display, sans-serif; font-weight: 500; font-size: 32px; line-height: 48px; @@ -282,168 +283,6 @@ $small-breakpoint: 960px; } .landing-page { - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr 2fr; - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - display: none; - } - - .column-1 { - grid-column: 1; - grid-row: 1; - } - - .column-2 { - grid-column: 2; - grid-row: 1; - } - - .column-3 { - grid-column: 3; - grid-row: 1 / 3; - } - - .column-4 { - grid-column: 1 / 3; - grid-row: 2; - } - } - - @media screen and (max-width: $small-breakpoint) { - .grid { - grid-template-columns: 40% 60%; - - .column-0 { - display: none; - } - - .column-1 { - grid-column: 1; - grid-row: 1; - - &.non-preview .landing-page__forms { - height: 100%; - } - } - - .column-2 { - grid-column: 2; - grid-row: 1 / 3; - - &.non-preview { - grid-column: 2; - grid-row: 1; - } - } - - .column-3 { - grid-column: 1; - grid-row: 2 / 4; - } - - .column-4 { - grid-column: 2; - grid-row: 3; - - &.non-preview { - grid-column: 1 / 3; - grid-row: 2; - } - } - } - } - - @media screen and (max-width: $column-breakpoint) { - .grid { - grid-template-columns: 100%; - - .column-0 { - display: block; - grid-column: 1; - grid-row: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - - .brand { - display: none; - } - } - - .column-2 { - grid-column: 1; - grid-row: 2; - - .landing-page__logo, - .landing-page__call-to-action { - display: none; - } - - &.non-preview { - grid-column: 1; - grid-row: 2; - } - } - - .column-3 { - grid-column: 1; - grid-row: 5; - } - - .column-4 { - grid-column: 1; - grid-row: 4; - - &.non-preview { - grid-column: 1; - grid-row: 4; - } - } - } - } - - .column-flex { - display: flex; - flex-direction: column; - } - - .separator-or { - position: relative; - margin: 40px 0; - text-align: center; - - &::before { - content: ""; - display: block; - width: 100%; - height: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, .6); - position: absolute; - top: 50%; - left: 0; - } - - span { - display: inline-block; - background: $ui-base-color; - font-size: 12px; - font-weight: 500; - color: $darker-text-color; - text-transform: uppercase; - position: relative; - z-index: 1; - padding: 0 8px; - cursor: default; - } - } - p, li { font-family: $font-sans-serif, sans-serif; @@ -460,28 +299,6 @@ $small-breakpoint: 960px; } } - .closed-registrations-message { - margin-top: 20px; - - &, - p { - text-align: center; - font-size: 12px; - line-height: 18px; - color: $darker-text-color; - margin-bottom: 0; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - - p:last-child { - margin-bottom: 0; - } - } - em { display: inline; margin: 0; @@ -595,187 +412,6 @@ $small-breakpoint: 960px; } } - .container-alt { - width: 100%; - box-sizing: border-box; - max-width: 800px; - margin: 0 auto; - word-wrap: break-word; - } - - .header-wrapper { - padding-top: 15px; - background: $ui-base-color; - background: linear-gradient(150deg, lighten($ui-base-color, 8%), $ui-base-color); - position: relative; - - &.compact { - background: $ui-base-color; - padding-bottom: 15px; - - .hero .heading { - padding-bottom: 20px; - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - } - } - - .brand { - a { - padding-left: 0; - padding-right: 0; - color: $white; - } - - img { - height: 32px; - position: relative; - top: 4px; - left: -10px; - } - } - - .header { - line-height: 30px; - overflow: hidden; - - .container-alt { - display: flex; - justify-content: space-between; - } - - .links { - position: relative; - z-index: 4; - - a { - display: flex; - justify-content: center; - align-items: center; - color: $darker-text-color; - text-decoration: none; - padding: 12px 16px; - line-height: 32px; - font-family: $font-display, sans-serif; - font-weight: 500; - font-size: 14px; - - &:hover { - color: $secondary-text-color; - } - } - - ul { - list-style: none; - margin: 0; - - li { - display: inline-block; - vertical-align: bottom; - margin: 0; - - &:first-child a { - padding-left: 0; - } - - &:last-child a { - padding-right: 0; - } - } - } - } - - .hero { - margin-top: 50px; - align-items: center; - position: relative; - - .heading { - position: relative; - z-index: 4; - padding-bottom: 150px; - } - - .simple_form, - .closed-registrations-message { - background: darken($ui-base-color, 4%); - width: 280px; - padding: 15px 20px; - border-radius: 4px 4px 0 0; - line-height: initial; - position: relative; - z-index: 4; - - .actions { - margin-bottom: 0; - - button, - .button, - .block-button { - margin-bottom: 0; - } - } - } - - .closed-registrations-message { - min-height: 330px; - display: flex; - flex-direction: column; - justify-content: space-between; - } - } - } - - .about-short { - background: darken($ui-base-color, 4%); - padding: 50px 0 30px; - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - - &.alternative { - padding: 10px 0; - - .brand { - text-align: center; - padding: 30px 0; - margin-bottom: 10px; - - img { - position: static; - padding: 10px 0; - } - - @media screen and (max-width: $small-breakpoint) { - padding: 15px 0; - } - - @media screen and (max-width: $column-breakpoint) { - padding: 0; - margin-bottom: -10px; - } - } - } - &__information, &__forms { padding: 20px; @@ -970,353 +606,253 @@ $small-breakpoint: 960px; } } - &__forms { - height: 100%; - - @media screen and (max-width: $small-breakpoint) { - height: auto; - } - - @media screen and (max-width: $column-breakpoint) { - background: transparent; - box-shadow: none; - padding: 0 20px; - margin-top: 30px; - margin-bottom: 40px; - - .separator-or { - span { - background: darken($ui-base-color, 8%); - } + @media screen and (max-width: 840px) { + .information-board { + .container-alt { + padding-right: 20px; } - } - - hr { - margin: 40px 0; - } - .button { - display: block; - } - - .subtle-hint a { - text-decoration: none; + .panel { + position: static; + margin-top: 20px; + width: 100%; + border-radius: 4px; - &:hover, - &:focus, - &:active { - text-decoration: underline; + .panel-header { + text-align: center; + } } } } - #mastodon-timeline { - display: flex; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - font-family: $font-sans-serif, sans-serif; - font-size: 13px; - line-height: 18px; - font-weight: 400; - color: $primary-text-color; - width: 100%; - flex: 1 1 auto; - overflow: hidden; - height: 100%; - - .column-header { - color: inherit; - font-family: inherit; - font-size: 16px; - line-height: inherit; - font-weight: inherit; - margin: 0; - padding: 0; - } - - .column { - padding: 0; - border-radius: 4px; - overflow: hidden; - width: 100%; - } - - .scrollable { - height: 400px; - } - - p { - font-size: inherit; - line-height: inherit; - font-weight: inherit; - color: $primary-text-color; - margin-bottom: 20px; - - &:last-child { - margin-bottom: 0; - } + @media screen and (max-width: 675px) { + .header-wrapper { + padding-top: 0; - a { - color: $secondary-text-color; - text-decoration: none; + &.compact { + padding-bottom: 0; } - } - - .attachment-list__list { - margin-left: 0; - list-style: none; - - li { - font-size: inherit; - line-height: inherit; - font-weight: inherit; - margin-bottom: 0; - - a { - color: $dark-text-color; - text-decoration: none; - &:hover { - text-decoration: underline; - } - } + &.compact .hero .heading { + text-align: initial; } } - @media screen and (max-width: $column-breakpoint) { - display: none; + .header .container-alt, + .features .container-alt { + display: block; } } - &__features { - & > p { - padding-right: 60px; - } - - .features-list { - margin: 40px 0; - margin-top: 30px; - } - - &__action { - text-align: center; - } + .cta { + margin: 20px; } +} - .features-list { - .features-list__row { - display: flex; - padding: 10px 0; - justify-content: space-between; - - .visual { - flex: 0 0 auto; - display: flex; - align-items: center; - margin-left: 15px; +.landing { + margin-bottom: 100px; - .fa { - display: block; - color: $darker-text-color; - font-size: 48px; - } - } + @media screen and (max-width: 738px) { + margin-bottom: 0; + } - .text { - font-size: 16px; - line-height: 30px; - color: $darker-text-color; + &__brand { + display: flex; + justify-content: center; + align-items: center; + padding: 100px; - h6 { - font-size: inherit; - line-height: inherit; - margin-bottom: 0; - } - } + img { + height: 52px; } - @media screen and (min-width: $small-breakpoint) { - display: grid; - grid-gap: 30px; - grid-template-columns: 1fr 1fr; - grid-auto-columns: 50%; - grid-auto-rows: max-content; + @media screen and (max-width: $no-gap-breakpoint) { + padding: 0; + margin-bottom: 30px; } } - .footer-links { - padding-bottom: 50px; - text-align: right; - color: $dark-text-color; + .directory { + margin-top: 30px; + background: transparent; + box-shadow: none; + border-radius: 0; + } - p { - font-size: 14px; - } + .hero-widget { + margin-top: 30px; + margin-bottom: 0; - a { - color: inherit; - text-decoration: underline; + h4 { + padding: 10px; + text-transform: uppercase; + font-weight: 700; + font-size: 13px; + color: $darker-text-color; } - } - &__footer { - margin-top: 10px; - text-align: center; - color: $dark-text-color; + &__text { + border-radius: 0; + padding-bottom: 0; + } - p { - font-size: 14px; + &__footer { + background: $ui-base-color; + padding: 10px; + border-radius: 0 0 4px 4px; + display: flex; - a { - color: inherit; - text-decoration: underline; + &__column { + flex: 1 1 50%; } } - } - @media screen and (max-width: 840px) { - .container-alt { - padding: 0 20px; - } + .account { + padding: 10px 0; + border-bottom: 0; - .information-board { - .container-alt { - padding-right: 20px; + .account__display-name { + display: flex; + align-items: center; } - .panel { - position: static; - margin-top: 20px; - width: 100%; - border-radius: 4px; - - .panel-header { - text-align: center; - } + .account__avatar { + width: 44px; + height: 44px; + background-size: 44px 44px; } } - } - @media screen and (max-width: 675px) { - .header-wrapper { - padding-top: 0; + &__counter { + padding: 10px; - &.compact { - padding-bottom: 0; + strong { + font-family: $font-display, sans-serif; + font-size: 15px; + font-weight: 700; + display: block; } - &.compact .hero .heading { - text-align: initial; + span { + font-size: 14px; + color: $darker-text-color; } } + } - .header .container-alt, - .features .container-alt { - display: block; - } - - .header { - .links { - padding-top: 15px; - background: darken($ui-base-color, 4%); + .simple_form .user_agreement .label_input > label { + font-weight: 400; + color: $darker-text-color; + } - a { - padding: 12px 8px; - } + .simple_form p.lead { + color: $darker-text-color; + font-size: 15px; + line-height: 20px; + font-weight: 400; + margin-bottom: 25px; + } - .nav { - display: flex; - flex-flow: row wrap; - justify-content: space-around; - } + &__grid { + max-width: 960px; + margin: 0 auto; + display: grid; + grid-template-columns: minmax(0, 50%) minmax(0, 50%); + grid-gap: 30px; - .brand img { - left: 0; - top: 0; - } - } + @media screen and (max-width: 738px) { + grid-template-columns: minmax(0, 100%); + grid-gap: 10px; - .hero { - margin-top: 30px; - padding: 0; + &__column-login { + grid-row: 1; + display: flex; + flex-direction: column; - .heading { - padding: 30px 20px; - text-align: center; + .box-widget { + order: 2; + flex: 0 0 auto; } - .simple_form, - .closed-registrations-message { - background: darken($ui-base-color, 8%); - width: 100%; - border-radius: 0; - box-sizing: border-box; + .hero-widget { + margin-top: 0; + margin-bottom: 10px; + order: 1; + flex: 0 0 auto; } } - } - } - - .cta { - margin: 20px; - } - &.tag-page { - @media screen and (max-width: $column-breakpoint) { - padding: 0; - - .container { - padding: 0; + &__column-registration { + grid-row: 2; } - #mastodon-timeline { - display: flex; - height: 100vh; - border-radius: 0; + .directory { + margin-top: 10px; } } - .grid { - @media screen and (min-width: $small-breakpoint) { - grid-template-columns: 33% 67%; - } - - .column-2 { - grid-column: 2; - grid-row: 1; - } - } + @media screen and (max-width: $no-gap-breakpoint) { + grid-gap: 0; - .brand { - text-align: unset; - padding: 0; + .hero-widget { + display: block; + margin-bottom: 0; + box-shadow: none; - img { - height: 48px; - width: auto; + &__img, + &__img img, + &__footer { + border-radius: 0; + } } - } - .cta { - margin: 0; - - .button { - margin-right: 4px; + .hero-widget, + .box-widget, + .directory__tag { + border-bottom: 1px solid lighten($ui-base-color, 8%); } - } - @media screen and (max-width: $column-breakpoint) { - .grid { - grid-gap: 0; + .directory { + margin-top: 0; - .column-1 { - grid-column: 1; - grid-row: 1; - } + &__tag { + margin-bottom: 0; - .column-2 { - display: none; + & > a, + & > div { + border-radius: 0; + box-shadow: none; + } + + &:last-child { + border-bottom: 0; + } } } } } } + +.brand { + position: relative; + text-decoration: none; +} + +.brand__tagline { + display: block; + position: absolute; + bottom: -10px; + left: 50px; + width: 300px; + color: $ui-primary-color; + text-decoration: none; + font-size: 14px; + + @media screen and (max-width: $no-gap-breakpoint) { + position: static; + width: auto; + margin-top: 20px; + color: $dark-text-color; + } +} + diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 4dbbaa1e8..42f53f525 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -705,3 +705,11 @@ a.name-tag, overflow: hidden; text-overflow: ellipsis; } + +.ellipsized-ip { + display: inline-block; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; +} diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index bab982706..6051c1d00 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -68,6 +68,17 @@ code { top: 2px; left: 0; } + + label a { + color: $highlight-text-color; + text-decoration: underline; + + &:hover, + &:active, + &:focus { + text-decoration: none; + } + } } } @@ -305,7 +316,7 @@ code { box-shadow: none; } - &:focus:invalid { + &:focus:invalid:not(:placeholder-shown) { border-color: lighten($error-red, 12%); } @@ -346,6 +357,10 @@ code { } } + .input.disabled { + opacity: 0.5; + } + .actions { margin-top: 30px; display: flex; @@ -392,6 +407,10 @@ code { background-color: darken($ui-highlight-color, 5%); } + &:disabled:hover { + background-color: $ui-primary-color; + } + &.negative { background: $error-value-color; diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 4f8c94d83..6030e1e98 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -144,6 +144,7 @@ button, select { + width: 100%; flex: 1 1 50%; } } @@ -190,3 +191,15 @@ color: darken($simple-background-color, 14%); } } + +.muted .poll { + color: $dark-text-color; + + &__chart { + background: rgba(darken($ui-primary-color, 14%), 0.2); + + &.leading { + background: rgba($ui-highlight-color, 0.2); + } + } +} diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss index 9fd0b95bb..296182ff5 100644 --- a/app/javascript/flavours/glitch/styles/tables.scss +++ b/app/javascript/flavours/glitch/styles/tables.scss @@ -82,6 +82,10 @@ } } } + + &--invites tbody td { + vertical-align: middle; + } } .table-wrapper { diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss index 1eaf30c5b..645192ea4 100644 --- a/app/javascript/flavours/glitch/styles/widgets.scss +++ b/app/javascript/flavours/glitch/styles/widgets.scss @@ -295,6 +295,11 @@ cursor: default; } + &.disabled > div { + opacity: 0.5; + cursor: default; + } + h4 { flex: 1 1 auto; font-size: 18px; diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 4c145febc..61fef19e9 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -92,7 +92,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); const excludeTypesFromFilter = filter => { - const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention']); + const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']); return allTypes.filterNot(item => item === filter).toJS(); }; diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js index a1a4bd024..54f8eb310 100644 --- a/app/javascript/mastodon/containers/timeline_container.js +++ b/app/javascript/mastodon/containers/timeline_container.js @@ -7,7 +7,6 @@ import { hydrateStore } from '../actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from '../locales'; import PublicTimeline from '../features/standalone/public_timeline'; -import CommunityTimeline from '../features/standalone/community_timeline'; import HashtagTimeline from '../features/standalone/hashtag_timeline'; import ModalContainer from '../features/ui/containers/modal_container'; import initialState from '../initial_state'; @@ -26,24 +25,22 @@ export default class TimelineContainer extends React.PureComponent { static propTypes = { locale: PropTypes.string.isRequired, hashtag: PropTypes.string, - showPublicTimeline: PropTypes.bool.isRequired, + local: PropTypes.bool, }; static defaultProps = { - showPublicTimeline: initialState.settings.known_fediverse, + local: !initialState.settings.known_fediverse, }; render () { - const { locale, hashtag, showPublicTimeline } = this.props; + const { locale, hashtag, local } = this.props; let timeline; if (hashtag) { timeline = <HashtagTimeline hashtag={hashtag} />; - } else if (showPublicTimeline) { - timeline = <PublicTimeline />; } else { - timeline = <CommunityTimeline />; + timeline = <PublicTimeline local={local} />; } return ( @@ -51,6 +48,7 @@ export default class TimelineContainer extends React.PureComponent { <Provider store={store}> <Fragment> {timeline} + {ReactDOM.createPortal( <ModalContainer />, document.getElementById('modal-container'), diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index a334fd63c..60a86312a 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -89,6 +89,17 @@ export default class ColumnSettings extends React.PureComponent { <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} /> </div> </div> + + <div role='group' aria-labelledby='notifications-poll'> + <span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span> + + <div className='column-settings__row'> + <SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} /> + </div> + </div> </div> ); } diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.js b/app/javascript/mastodon/features/notifications/components/filter_bar.js index 6ae8b7491..3f3e6ab7d 100644 --- a/app/javascript/mastodon/features/notifications/components/filter_bar.js +++ b/app/javascript/mastodon/features/notifications/components/filter_bar.js @@ -7,6 +7,7 @@ const tooltips = defineMessages({ mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' }, favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' }, boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' }, + polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' }, follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' }, }); @@ -80,6 +81,13 @@ class FilterBar extends React.PureComponent { <Icon id='retweet' fixedWidth /> </button> <button + className={selectedFilter === 'poll' ? 'active' : ''} + onClick={this.onClick('poll')} + title={intl.formatMessage(tooltips.polls)} + > + <Icon id='tasks' fixedWidth /> + </button> + <button className={selectedFilter === 'follow' ? 'active' : ''} onClick={this.onClick('follow')} title={intl.formatMessage(tooltips.follows)} diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 9669b6e7d..4bdf09166 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -205,6 +205,38 @@ class Notification extends ImmutablePureComponent { ); } + renderPoll (notification) { + const { intl } = this.props; + + return ( + <HotKeys handlers={this.getHandlers()}> + <div className='notification notification-poll focusable' tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }), notification.get('created_at'))}> + <div className='notification__message'> + <div className='notification__favourite-icon-wrapper'> + <Icon id='tasks' fixedWidth /> + </div> + + <span title={notification.get('created_at')}> + <FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' /> + </span> + </div> + + <StatusContainer + id={notification.get('status')} + account={notification.get('account')} + muted + withDismiss + hidden={this.props.hidden} + getScrollPosition={this.props.getScrollPosition} + updateScrollBottom={this.props.updateScrollBottom} + cachedMediaWidth={this.props.cachedMediaWidth} + cacheMediaWidth={this.props.cacheMediaWidth} + /> + </div> + </HotKeys> + ); + } + render () { const { notification } = this.props; const account = notification.get('account'); @@ -220,6 +252,8 @@ class Notification extends ImmutablePureComponent { return this.renderFavourite(notification, link); case 'reblog': return this.renderReblog(notification, link); + case 'poll': + return this.renderPoll(notification); } return null; diff --git a/app/javascript/mastodon/features/standalone/community_timeline/index.js b/app/javascript/mastodon/features/standalone/community_timeline/index.js deleted file mode 100644 index f917f41c9..000000000 --- a/app/javascript/mastodon/features/standalone/community_timeline/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from '../../ui/containers/status_list_container'; -import { expandCommunityTimeline } from '../../../actions/timelines'; -import Column from '../../../components/column'; -import ColumnHeader from '../../../components/column_header'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connectCommunityStream } from '../../../actions/streaming'; - -const messages = defineMessages({ - title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, -}); - -export default @connect() -@injectIntl -class CommunityTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - } - - setRef = c => { - this.column = c; - } - - componentDidMount () { - const { dispatch } = this.props; - - dispatch(expandCommunityTimeline()); - this.disconnect = dispatch(connectCommunityStream()); - } - - componentWillUnmount () { - if (this.disconnect) { - this.disconnect(); - this.disconnect = null; - } - } - - handleLoadMore = maxId => { - this.props.dispatch(expandCommunityTimeline({ maxId })); - } - - render () { - const { intl } = this.props; - - return ( - <Column ref={this.setRef} label={intl.formatMessage(messages.title)}> - <ColumnHeader - icon='users' - title={intl.formatMessage(messages.title)} - onClick={this.handleHeaderClick} - /> - - <StatusListContainer - timelineId='community' - onLoadMore={this.handleLoadMore} - scrollKey='standalone_public_timeline' - trackScroll={false} - /> - </Column> - ); - } - -} diff --git a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js index 333726f94..0880d98c8 100644 --- a/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/hashtag_timeline/index.js @@ -2,13 +2,13 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { expandHashtagTimeline } from '../../../actions/timelines'; -import { connectHashtagStream } from '../../../actions/streaming'; +import { expandHashtagTimeline } from 'mastodon/actions/timelines'; +import { connectHashtagStream } from 'mastodon/actions/streaming'; import Masonry from 'react-masonry-infinite'; import { List as ImmutableList } from 'immutable'; -import DetailedStatusContainer from '../../status/containers/detailed_status_container'; +import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container'; import { debounce } from 'lodash'; -import LoadingIndicator from '../../../components/loading_indicator'; +import LoadingIndicator from 'mastodon/components/loading_indicator'; const mapStateToProps = (state, { hashtag }) => ({ statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()), diff --git a/app/javascript/mastodon/features/standalone/public_timeline/index.js b/app/javascript/mastodon/features/standalone/public_timeline/index.js index 618696eb1..10129e606 100644 --- a/app/javascript/mastodon/features/standalone/public_timeline/index.js +++ b/app/javascript/mastodon/features/standalone/public_timeline/index.js @@ -1,70 +1,112 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import StatusListContainer from '../../ui/containers/status_list_container'; -import { expandPublicTimeline } from '../../../actions/timelines'; -import Column from '../../../components/column'; -import ColumnHeader from '../../../components/column_header'; -import { defineMessages, injectIntl } from 'react-intl'; -import { connectPublicStream } from '../../../actions/streaming'; - -const messages = defineMessages({ - title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, -}); - -export default @connect() -@injectIntl +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines'; +import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming'; +import Masonry from 'react-masonry-infinite'; +import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; +import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container'; +import { debounce } from 'lodash'; +import LoadingIndicator from 'mastodon/components/loading_indicator'; + +const mapStateToProps = (state, { local }) => { + const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap()); + + return { + statusIds: timeline.get('items', ImmutableList()), + isLoading: timeline.get('isLoading', false), + hasMore: timeline.get('hasMore', false), + }; +}; + +export default @connect(mapStateToProps) class PublicTimeline extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + statusIds: ImmutablePropTypes.list.isRequired, + isLoading: PropTypes.bool.isRequired, + hasMore: PropTypes.bool.isRequired, + local: PropTypes.bool, }; - handleHeaderClick = () => { - this.column.scrollTop(); + componentDidMount () { + this._connect(); } - setRef = c => { - this.column = c; + componentDidUpdate (prevProps) { + if (prevProps.local !== this.props.local) { + this._disconnect(); + this._connect(); + } } - componentDidMount () { - const { dispatch } = this.props; + componentWillUnmount () { + this._disconnect(); + } + + _connect () { + const { dispatch, local } = this.props; - dispatch(expandPublicTimeline()); - this.disconnect = dispatch(connectPublicStream()); + dispatch(local ? expandCommunityTimeline() : expandPublicTimeline()); + this.disconnect = dispatch(local ? connectCommunityStream() : connectPublicStream()); } - componentWillUnmount () { + _disconnect () { if (this.disconnect) { this.disconnect(); this.disconnect = null; } } - handleLoadMore = maxId => { - this.props.dispatch(expandPublicTimeline({ maxId })); + handleLoadMore = () => { + const { dispatch, statusIds, local } = this.props; + const maxId = statusIds.last(); + + if (maxId) { + dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId })); + } + } + + setRef = c => { + this.masonry = c; } + handleHeightChange = debounce(() => { + if (!this.masonry) { + return; + } + + this.masonry.forcePack(); + }, 50) + render () { - const { intl } = this.props; + const { statusIds, hasMore, isLoading } = this.props; + + const sizes = [ + { columns: 1, gutter: 0 }, + { mq: '415px', columns: 1, gutter: 10 }, + { mq: '640px', columns: 2, gutter: 10 }, + { mq: '960px', columns: 3, gutter: 10 }, + { mq: '1255px', columns: 3, gutter: 10 }, + ]; + + const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined; return ( - <Column ref={this.setRef} label={intl.formatMessage(messages.title)}> - <ColumnHeader - icon='globe' - title={intl.formatMessage(messages.title)} - onClick={this.handleHeaderClick} - /> - - <StatusListContainer - timelineId='public' - onLoadMore={this.handleLoadMore} - scrollKey='standalone_public_timeline' - trackScroll={false} - /> - </Column> + <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}> + {statusIds.map(statusId => ( + <div className='statuses-grid__item' key={statusId}> + <DetailedStatusContainer + id={statusId} + compact + measureHeight + onHeightChange={this.handleHeightChange} + /> + </div> + )).toArray()} + </Masonry> ); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 5cd50f055..5c79f9f19 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -23,7 +23,7 @@ export default class DetailedStatus extends ImmutablePureComponent { }; static propTypes = { - status: ImmutablePropTypes.map.isRequired, + status: ImmutablePropTypes.map, onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, onToggleHidden: PropTypes.func.isRequired, diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 868e54751..78a5648af 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -17,6 +17,10 @@ { "defaultMessage": "File upload limit exceeded.", "id": "upload_error.limit" + }, + { + "defaultMessage": "File upload not allowed with polls.", + "id": "upload_error.poll" } ], "path": "app/javascript/mastodon/actions/compose.json" @@ -910,6 +914,52 @@ { "descriptors": [ { + "defaultMessage": "Add a poll", + "id": "poll_button.add_poll" + }, + { + "defaultMessage": "Remove poll", + "id": "poll_button.remove_poll" + } + ], + "path": "app/javascript/mastodon/features/compose/components/poll_button.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Choice {number}", + "id": "compose_form.poll.option_placeholder" + }, + { + "defaultMessage": "Add a choice", + "id": "compose_form.poll.add_option" + }, + { + "defaultMessage": "Remove this choice", + "id": "compose_form.poll.remove_option" + }, + { + "defaultMessage": "Poll duration", + "id": "compose_form.poll.duration" + }, + { + "defaultMessage": "{number, plural, one {# minute} other {# minutes}}", + "id": "intervals.full.minutes" + }, + { + "defaultMessage": "{number, plural, one {# hour} other {# hours}}", + "id": "intervals.full.hours" + }, + { + "defaultMessage": "{number, plural, one {# day} other {# days}}", + "id": "intervals.full.days" + } + ], + "path": "app/javascript/mastodon/features/compose/components/poll_form.json" + }, + { + "descriptors": [ + { "defaultMessage": "Public", "id": "privacy.public.short" }, @@ -1853,6 +1903,10 @@ { "defaultMessage": "{name} boosted your status", "id": "notification.reblog" + }, + { + "defaultMessage": "Your poll has ended", + "id": "notification.poll" } ], "path": "app/javascript/mastodon/features/notifications/components/notification.json" @@ -1917,24 +1971,6 @@ { "descriptors": [ { - "defaultMessage": "A look inside...", - "id": "standalone.public_title" - } - ], - "path": "app/javascript/mastodon/features/standalone/community_timeline/index.json" - }, - { - "descriptors": [ - { - "defaultMessage": "A look inside...", - "id": "standalone.public_title" - } - ], - "path": "app/javascript/mastodon/features/standalone/public_timeline/index.json" - }, - { - "descriptors": [ - { "defaultMessage": "Delete", "id": "status.delete" }, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ae37322e8..eb82cd9a9 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -77,6 +77,10 @@ "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", "compose_form.lock_disclaimer.lock": "locked", "compose_form.placeholder": "What's on your mind?", + "compose_form.poll.add_option": "Add a choice", + "compose_form.poll.duration": "Poll duration", + "compose_form.poll.option_placeholder": "Choice {number}", + "compose_form.poll.remove_option": "Remove this choice", "compose_form.publish": "Toot", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Media is marked as sensitive", @@ -155,6 +159,9 @@ "home.column_settings.basic": "Basic", "home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_replies": "Show replies", + "intervals.full.days": "{number, plural, one {# day} other {# days}}", + "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", + "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", "introduction.federation.action": "Next", "introduction.federation.federated.headline": "Federated", "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.", @@ -245,6 +252,7 @@ "notification.favourite": "{name} favourited your status", "notification.follow": "{name} followed you", "notification.mention": "{name} mentioned you", + "notification.poll": "A poll you have voted in has ended", "notification.reblog": "{name} boosted your status", "notifications.clear": "Clear notifications", "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", @@ -269,6 +277,8 @@ "poll.refresh": "Refresh", "poll.total_votes": "{count, plural, one {# vote} other {# votes}}", "poll.vote": "Vote", + "poll_button.add_poll": "Add a poll", + "poll_button.remove_poll": "Remove poll", "privacy.change": "Adjust status privacy", "privacy.direct.long": "Post to mentioned users only", "privacy.direct.short": "Direct", @@ -303,7 +313,6 @@ "search_results.hashtags": "Hashtags", "search_results.statuses": "Toots", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", - "standalone.public_title": "A look inside...", "status.admin_account": "Open moderation interface for @{name}", "status.admin_status": "Open this status in the moderation interface", "status.block": "Block @{name}", @@ -361,6 +370,7 @@ "upload_area.title": "Drag & drop to upload", "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_error.limit": "File upload limit exceeded.", + "upload_error.poll": "File upload not allowed with polls.", "upload_form.description": "Describe for the visually impaired", "upload_form.focus": "Change preview", "upload_form.undo": "Delete", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index e2d3a98f3..6388c7e9c 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -77,6 +77,10 @@ "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", "compose_form.lock_disclaimer.lock": "承認制", "compose_form.placeholder": "今なにしてる?", + "compose_form.poll.add_option": "Add a choice", + "compose_form.poll.duration": "Poll duration", + "compose_form.poll.option_placeholder": "Choice {number}", + "compose_form.poll.remove_option": "Remove this choice", "compose_form.publish": "トゥート", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "メディアに閲覧注意が設定されています", @@ -155,6 +159,9 @@ "home.column_settings.basic": "基本設定", "home.column_settings.show_reblogs": "ブースト表示", "home.column_settings.show_replies": "返信表示", + "intervals.full.days": "{number, plural, one {# day} other {# days}}", + "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", + "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", "introduction.federation.action": "次へ", "introduction.federation.federated.headline": "連合タイムライン", "introduction.federation.federated.text": "Fediverseの他のサーバーからの公開投稿は連合タイムラインに表示されます。", @@ -245,6 +252,7 @@ "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました", "notification.follow": "{name}さんにフォローされました", "notification.mention": "{name}さんがあなたに返信しました", + "notification.poll": "Your poll has ended", "notification.reblog": "{name}さんがあなたのトゥートをブーストしました", "notifications.clear": "通知を消去", "notifications.clear_confirmation": "本当に通知を消去しますか?", @@ -269,6 +277,8 @@ "poll.refresh": "Refresh", "poll.total_votes": "{count, plural, one {# vote} other {# votes}}", "poll.vote": "Vote", + "poll_button.add_poll": "Add a poll", + "poll_button.remove_poll": "Remove poll", "privacy.change": "公開範囲を変更", "privacy.direct.long": "メンションしたユーザーだけに公開", "privacy.direct.short": "ダイレクト", @@ -303,7 +313,6 @@ "search_results.hashtags": "ハッシュタグ", "search_results.statuses": "トゥート", "search_results.total": "{count, number}件の結果", - "standalone.public_title": "今こんな話をしています...", "status.admin_account": "@{name} のモデレーション画面を開く", "status.admin_status": "このトゥートをモデレーション画面で開く", "status.block": "@{name}さんをブロック", @@ -361,6 +370,7 @@ "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_error.limit": "アップロードできる上限を超えています。", + "upload_error.poll": "File upload not allowed with polls.", "upload_form.description": "視覚障害者のための説明", "upload_form.focus": "焦点", "upload_form.undo": "削除", diff --git a/app/javascript/mastodon/reducers/push_notifications.js b/app/javascript/mastodon/reducers/push_notifications.js index 85628c6b1..317352b79 100644 --- a/app/javascript/mastodon/reducers/push_notifications.js +++ b/app/javascript/mastodon/reducers/push_notifications.js @@ -9,6 +9,7 @@ const initialState = Immutable.Map({ favourite: false, reblog: false, mention: false, + poll: false, }), isSubscribed: false, browserSupport: false, diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 2e1878cf7..a0eea137f 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -31,6 +31,7 @@ const initialState = ImmutableMap({ favourite: true, reblog: true, mention: true, + poll: true, }), quickFilter: ImmutableMap({ @@ -44,6 +45,7 @@ const initialState = ImmutableMap({ favourite: true, reblog: true, mention: true, + poll: true, }), sounds: ImmutableMap({ @@ -51,6 +53,7 @@ const initialState = ImmutableMap({ favourite: true, reblog: true, mention: true, + poll: true, }), }), diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js index ce96ae297..5ce8c7b50 100644 --- a/app/javascript/mastodon/service_worker/web_push_locales.js +++ b/app/javascript/mastodon/service_worker/web_push_locales.js @@ -18,6 +18,7 @@ filenames.forEach(filename => { 'notification.follow': full['notification.follow'] || '', 'notification.mention': full['notification.mention'] || '', 'notification.reblog': full['notification.reblog'] || '', + 'notification.poll': full['notification.poll'] || '', 'status.show_more': full['status.show_more'] || '', 'status.reblog': full['status.reblog'] || '', diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index b078d4d24..465ef2c11 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -193,6 +193,7 @@ $small-breakpoint: 960px; } strong { + font-family: $font-display, sans-serif; font-weight: 500; font-size: 32px; line-height: 48px; @@ -280,168 +281,6 @@ $small-breakpoint: 960px; } .landing-page { - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr 2fr; - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - display: none; - } - - .column-1 { - grid-column: 1; - grid-row: 1; - } - - .column-2 { - grid-column: 2; - grid-row: 1; - } - - .column-3 { - grid-column: 3; - grid-row: 1 / 3; - } - - .column-4 { - grid-column: 1 / 3; - grid-row: 2; - } - } - - @media screen and (max-width: $small-breakpoint) { - .grid { - grid-template-columns: 40% 60%; - - .column-0 { - display: none; - } - - .column-1 { - grid-column: 1; - grid-row: 1; - - &.non-preview .landing-page__forms { - height: 100%; - } - } - - .column-2 { - grid-column: 2; - grid-row: 1 / 3; - - &.non-preview { - grid-column: 2; - grid-row: 1; - } - } - - .column-3 { - grid-column: 1; - grid-row: 2 / 4; - } - - .column-4 { - grid-column: 2; - grid-row: 3; - - &.non-preview { - grid-column: 1 / 3; - grid-row: 2; - } - } - } - } - - @media screen and (max-width: $column-breakpoint) { - .grid { - grid-template-columns: 100%; - - .column-0 { - display: block; - grid-column: 1; - grid-row: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - - .brand { - display: none; - } - } - - .column-2 { - grid-column: 1; - grid-row: 2; - - .landing-page__logo, - .landing-page__call-to-action { - display: none; - } - - &.non-preview { - grid-column: 1; - grid-row: 2; - } - } - - .column-3 { - grid-column: 1; - grid-row: 5; - } - - .column-4 { - grid-column: 1; - grid-row: 4; - - &.non-preview { - grid-column: 1; - grid-row: 4; - } - } - } - } - - .column-flex { - display: flex; - flex-direction: column; - } - - .separator-or { - position: relative; - margin: 40px 0; - text-align: center; - - &::before { - content: ""; - display: block; - width: 100%; - height: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, .6); - position: absolute; - top: 50%; - left: 0; - } - - span { - display: inline-block; - background: $ui-base-color; - font-size: 12px; - font-weight: 500; - color: $darker-text-color; - text-transform: uppercase; - position: relative; - z-index: 1; - padding: 0 8px; - cursor: default; - } - } - p, li { font-family: $font-sans-serif, sans-serif; @@ -458,28 +297,6 @@ $small-breakpoint: 960px; } } - .closed-registrations-message { - margin-top: 20px; - - &, - p { - text-align: center; - font-size: 12px; - line-height: 18px; - color: $darker-text-color; - margin-bottom: 0; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - - p:last-child { - margin-bottom: 0; - } - } - em { display: inline; margin: 0; @@ -593,187 +410,6 @@ $small-breakpoint: 960px; } } - .container-alt { - width: 100%; - box-sizing: border-box; - max-width: 800px; - margin: 0 auto; - word-wrap: break-word; - } - - .header-wrapper { - padding-top: 15px; - background: $ui-base-color; - background: linear-gradient(150deg, lighten($ui-base-color, 8%), $ui-base-color); - position: relative; - - &.compact { - background: $ui-base-color; - padding-bottom: 15px; - - .hero .heading { - padding-bottom: 20px; - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - } - } - - .brand { - a { - padding-left: 0; - padding-right: 0; - color: $white; - } - - img { - height: 32px; - position: relative; - top: 4px; - left: -10px; - } - } - - .header { - line-height: 30px; - overflow: hidden; - - .container-alt { - display: flex; - justify-content: space-between; - } - - .links { - position: relative; - z-index: 4; - - a { - display: flex; - justify-content: center; - align-items: center; - color: $darker-text-color; - text-decoration: none; - padding: 12px 16px; - line-height: 32px; - font-family: $font-display, sans-serif; - font-weight: 500; - font-size: 14px; - - &:hover { - color: $secondary-text-color; - } - } - - ul { - list-style: none; - margin: 0; - - li { - display: inline-block; - vertical-align: bottom; - margin: 0; - - &:first-child a { - padding-left: 0; - } - - &:last-child a { - padding-right: 0; - } - } - } - } - - .hero { - margin-top: 50px; - align-items: center; - position: relative; - - .heading { - position: relative; - z-index: 4; - padding-bottom: 150px; - } - - .simple_form, - .closed-registrations-message { - background: darken($ui-base-color, 4%); - width: 280px; - padding: 15px 20px; - border-radius: 4px 4px 0 0; - line-height: initial; - position: relative; - z-index: 4; - - .actions { - margin-bottom: 0; - - button, - .button, - .block-button { - margin-bottom: 0; - } - } - } - - .closed-registrations-message { - min-height: 330px; - display: flex; - flex-direction: column; - justify-content: space-between; - } - } - } - - .about-short { - background: darken($ui-base-color, 4%); - padding: 50px 0 30px; - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - - &.alternative { - padding: 10px 0; - - .brand { - text-align: center; - padding: 30px 0; - margin-bottom: 10px; - - img { - position: static; - padding: 10px 0; - } - - @media screen and (max-width: $small-breakpoint) { - padding: 15px 0; - } - - @media screen and (max-width: $column-breakpoint) { - padding: 0; - margin-bottom: -10px; - } - } - } - &__information, &__forms { padding: 20px; @@ -967,353 +603,253 @@ $small-breakpoint: 960px; } } - &__forms { - height: 100%; - - @media screen and (max-width: $small-breakpoint) { - height: auto; - } - - @media screen and (max-width: $column-breakpoint) { - background: transparent; - box-shadow: none; - padding: 0 20px; - margin-top: 30px; - margin-bottom: 40px; - - .separator-or { - span { - background: darken($ui-base-color, 8%); - } + @media screen and (max-width: 840px) { + .information-board { + .container-alt { + padding-right: 20px; } - } - - hr { - margin: 40px 0; - } - .button { - display: block; - } - - .subtle-hint a { - text-decoration: none; + .panel { + position: static; + margin-top: 20px; + width: 100%; + border-radius: 4px; - &:hover, - &:focus, - &:active { - text-decoration: underline; + .panel-header { + text-align: center; + } } } } - #mastodon-timeline { - display: flex; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - font-family: $font-sans-serif, sans-serif; - font-size: 13px; - line-height: 18px; - font-weight: 400; - color: $primary-text-color; - width: 100%; - flex: 1 1 auto; - overflow: hidden; - height: 100%; - - .column-header { - color: inherit; - font-family: inherit; - font-size: 16px; - line-height: inherit; - font-weight: inherit; - margin: 0; - padding: 0; - } - - .column { - padding: 0; - border-radius: 4px; - overflow: hidden; - width: 100%; - } - - .scrollable { - height: 400px; - } - - p { - font-size: inherit; - line-height: inherit; - font-weight: inherit; - color: $primary-text-color; - margin-bottom: 20px; - - &:last-child { - margin-bottom: 0; - } + @media screen and (max-width: 675px) { + .header-wrapper { + padding-top: 0; - a { - color: $secondary-text-color; - text-decoration: none; + &.compact { + padding-bottom: 0; } - } - - .attachment-list__list { - margin-left: 0; - list-style: none; - - li { - font-size: inherit; - line-height: inherit; - font-weight: inherit; - margin-bottom: 0; - - a { - color: $dark-text-color; - text-decoration: none; - &:hover { - text-decoration: underline; - } - } + &.compact .hero .heading { + text-align: initial; } } - @media screen and (max-width: $column-breakpoint) { - display: none; + .header .container-alt, + .features .container-alt { + display: block; } } - &__features { - & > p { - padding-right: 60px; - } - - .features-list { - margin: 40px 0; - margin-top: 30px; - } - - &__action { - text-align: center; - } + .cta { + margin: 20px; } +} - .features-list { - .features-list__row { - display: flex; - padding: 10px 0; - justify-content: space-between; - - .visual { - flex: 0 0 auto; - display: flex; - align-items: center; - margin-left: 15px; +.landing { + margin-bottom: 100px; - .fa { - display: block; - color: $darker-text-color; - font-size: 48px; - } - } + @media screen and (max-width: 738px) { + margin-bottom: 0; + } - .text { - font-size: 16px; - line-height: 30px; - color: $darker-text-color; + &__brand { + display: flex; + justify-content: center; + align-items: center; + padding: 100px; - h6 { - font-size: inherit; - line-height: inherit; - margin-bottom: 0; - } - } + img { + height: 52px; } - @media screen and (min-width: $small-breakpoint) { - display: grid; - grid-gap: 30px; - grid-template-columns: 1fr 1fr; - grid-auto-columns: 50%; - grid-auto-rows: max-content; + @media screen and (max-width: $no-gap-breakpoint) { + padding: 0; + margin-bottom: 30px; } } - .footer-links { - padding-bottom: 50px; - text-align: right; - color: $dark-text-color; + .directory { + margin-top: 30px; + background: transparent; + box-shadow: none; + border-radius: 0; + } - p { - font-size: 14px; - } + .hero-widget { + margin-top: 30px; + margin-bottom: 0; - a { - color: inherit; - text-decoration: underline; + h4 { + padding: 10px; + text-transform: uppercase; + font-weight: 700; + font-size: 13px; + color: $darker-text-color; } - } - &__footer { - margin-top: 10px; - text-align: center; - color: $dark-text-color; + &__text { + border-radius: 0; + padding-bottom: 0; + } - p { - font-size: 14px; + &__footer { + background: $ui-base-color; + padding: 10px; + border-radius: 0 0 4px 4px; + display: flex; - a { - color: inherit; - text-decoration: underline; + &__column { + flex: 1 1 50%; } } - } - @media screen and (max-width: 840px) { - .container-alt { - padding: 0 20px; - } + .account { + padding: 10px 0; + border-bottom: 0; - .information-board { - .container-alt { - padding-right: 20px; + .account__display-name { + display: flex; + align-items: center; } - .panel { - position: static; - margin-top: 20px; - width: 100%; - border-radius: 4px; - - .panel-header { - text-align: center; - } + .account__avatar { + width: 44px; + height: 44px; + background-size: 44px 44px; } } - } - @media screen and (max-width: 675px) { - .header-wrapper { - padding-top: 0; + &__counter { + padding: 10px; - &.compact { - padding-bottom: 0; + strong { + font-family: $font-display, sans-serif; + font-size: 15px; + font-weight: 700; + display: block; } - &.compact .hero .heading { - text-align: initial; + span { + font-size: 14px; + color: $darker-text-color; } } + } - .header .container-alt, - .features .container-alt { - display: block; - } - - .header { - .links { - padding-top: 15px; - background: darken($ui-base-color, 4%); + .simple_form .user_agreement .label_input > label { + font-weight: 400; + color: $darker-text-color; + } - a { - padding: 12px 8px; - } + .simple_form p.lead { + color: $darker-text-color; + font-size: 15px; + line-height: 20px; + font-weight: 400; + margin-bottom: 25px; + } - .nav { - display: flex; - flex-flow: row wrap; - justify-content: space-around; - } + &__grid { + max-width: 960px; + margin: 0 auto; + display: grid; + grid-template-columns: minmax(0, 50%) minmax(0, 50%); + grid-gap: 30px; - .brand img { - left: 0; - top: 0; - } - } + @media screen and (max-width: 738px) { + grid-template-columns: minmax(0, 100%); + grid-gap: 10px; - .hero { - margin-top: 30px; - padding: 0; + &__column-login { + grid-row: 1; + display: flex; + flex-direction: column; - .heading { - padding: 30px 20px; - text-align: center; + .box-widget { + order: 2; + flex: 0 0 auto; } - .simple_form, - .closed-registrations-message { - background: darken($ui-base-color, 8%); - width: 100%; - border-radius: 0; - box-sizing: border-box; + .hero-widget { + margin-top: 0; + margin-bottom: 10px; + order: 1; + flex: 0 0 auto; } } - } - } - - .cta { - margin: 20px; - } - &.tag-page { - @media screen and (max-width: $column-breakpoint) { - padding: 0; - - .container { - padding: 0; + &__column-registration { + grid-row: 2; } - #mastodon-timeline { - display: flex; - height: 100vh; - border-radius: 0; + .directory { + margin-top: 10px; } } - .grid { - @media screen and (min-width: $small-breakpoint) { - grid-template-columns: 33% 67%; - } - - .column-2 { - grid-column: 2; - grid-row: 1; - } - } + @media screen and (max-width: $no-gap-breakpoint) { + grid-gap: 0; - .brand { - text-align: unset; - padding: 0; + .hero-widget { + display: block; + margin-bottom: 0; + box-shadow: none; - img { - height: 48px; - width: auto; + &__img, + &__img img, + &__footer { + border-radius: 0; + } } - } - .cta { - margin: 0; - - .button { - margin-right: 4px; + .hero-widget, + .box-widget, + .directory__tag { + border-bottom: 1px solid lighten($ui-base-color, 8%); } - } - @media screen and (max-width: $column-breakpoint) { - .grid { - grid-gap: 0; + .directory { + margin-top: 0; - .column-1 { - grid-column: 1; - grid-row: 1; - } + &__tag { + margin-bottom: 0; - .column-2 { - display: none; + & > a, + & > div { + border-radius: 0; + box-shadow: none; + } + + &:last-child { + border-bottom: 0; + } } } } } } + +.brand { + position: relative; + text-decoration: none; +} + +.brand__tagline { + display: block; + position: absolute; + bottom: -10px; + left: 50px; + width: 300px; + color: $ui-primary-color; + text-decoration: none; + font-size: 14px; + + @media screen and (max-width: $no-gap-breakpoint) { + position: static; + width: auto; + margin-top: 20px; + color: $dark-text-color; + } +} + diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 4dbbaa1e8..42f53f525 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -705,3 +705,11 @@ a.name-tag, overflow: hidden; text-overflow: ellipsis; } + +.ellipsized-ip { + display: inline-block; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; +} diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index bab982706..6051c1d00 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -68,6 +68,17 @@ code { top: 2px; left: 0; } + + label a { + color: $highlight-text-color; + text-decoration: underline; + + &:hover, + &:active, + &:focus { + text-decoration: none; + } + } } } @@ -305,7 +316,7 @@ code { box-shadow: none; } - &:focus:invalid { + &:focus:invalid:not(:placeholder-shown) { border-color: lighten($error-red, 12%); } @@ -346,6 +357,10 @@ code { } } + .input.disabled { + opacity: 0.5; + } + .actions { margin-top: 30px; display: flex; @@ -392,6 +407,10 @@ code { background-color: darken($ui-highlight-color, 5%); } + &:disabled:hover { + background-color: $ui-primary-color; + } + &.negative { background: $error-value-color; diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index 4f8c94d83..d8bc5473a 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -190,3 +190,15 @@ color: darken($simple-background-color, 14%); } } + +.muted .poll { + color: $dark-text-color; + + &__chart { + background: rgba(darken($ui-primary-color, 14%), 0.2); + + &.leading { + background: rgba($ui-highlight-color, 0.2); + } + } +} diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index adb75afe5..9e8785679 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -82,6 +82,10 @@ } } } + + &--invites tbody td { + vertical-align: middle; + } } .table-wrapper { diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 1eaf30c5b..645192ea4 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -295,6 +295,11 @@ cursor: default; } + &.disabled > div { + opacity: 0.5; + cursor: default; + } + h4 { flex: 1 1 auto; font-size: 18px; |