diff options
author | Thibaut Girka <thib@sitedethib.com> | 2019-02-15 18:02:45 +0100 |
---|---|---|
committer | Thibaut Girka <thib@sitedethib.com> | 2019-02-15 18:02:45 +0100 |
commit | 06cc04fd239896d294210b8bbaadb7c611aaffe5 (patch) | |
tree | 1868711bbe9ee79b25ff10a585457cfa0f9ac121 /app/javascript | |
parent | 2769b5446681898e8332cb505f499dac2a6e5717 (diff) | |
parent | 8ef50706a11e115e8b4aa31b30de93738bc7e754 (diff) |
Merge branch 'master' into glitch-soc/merge-upstream
Diffstat (limited to 'app/javascript')
13 files changed, 253 insertions, 93 deletions
diff --git a/app/javascript/mastodon/components/display_name.js b/app/javascript/mastodon/components/display_name.js index acddf77c5..32809778a 100644 --- a/app/javascript/mastodon/components/display_name.js +++ b/app/javascript/mastodon/components/display_name.js @@ -11,26 +11,36 @@ export default class DisplayName extends React.PureComponent { }; render () { - const { account, others, localDomain } = this.props; - const displayNameHtml = { __html: account.get('display_name_html') }; + const { others, localDomain } = this.props; - let suffix; + let displayName, suffix, account; if (others && others.size > 1) { - suffix = `+${others.size}`; + displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]); + + if (others.size - 2 > 0) { + suffix = `+${others.size - 2}`; + } } else { + if (others) { + account = others.first(); + } else { + account = this.props.account; + } + let acct = account.get('acct'); if (acct.indexOf('@') === -1 && localDomain) { acct = `${acct}@${localDomain}`; } - suffix = <span className='display-name__account'>@{acct}</span>; + displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>; + suffix = <span className='display-name__account'>@{acct}</span>; } return ( <span className='display-name'> - <bdi><strong className='display-name__html' dangerouslySetInnerHTML={displayNameHtml} /></bdi> {suffix} + {displayName} {suffix} </span> ); } diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 386404b57..3e98d374b 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -86,7 +86,7 @@ class Status extends ImmutablePureComponent { // Track height changes we know about to compensate scrolling componentDidMount () { - this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status.get('card'); + this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); } getSnapshotBeforeUpdate () { @@ -99,7 +99,7 @@ class Status extends ImmutablePureComponent { // Compensate height changes componentDidUpdate (prevProps, prevState, snapshot) { - const doShowCard = !this.props.muted && !this.props.hidden && this.props.status.get('card'); + const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card'); if (doShowCard && !this.didShowCard) { this.didShowCard = true; if (snapshot !== null && this.props.updateScrollBottom) { diff --git a/app/javascript/mastodon/features/compose/components/upload.js b/app/javascript/mastodon/features/compose/components/upload.js index 038d7ee28..629cbc36a 100644 --- a/app/javascript/mastodon/features/compose/components/upload.js +++ b/app/javascript/mastodon/features/compose/components/upload.js @@ -108,9 +108,8 @@ class Upload extends ImmutablePureComponent { <label> <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span> - <input + <textarea placeholder={intl.formatMessage(messages.description)} - type='text' value={description} maxLength={420} onFocus={this.handleInputFocus} diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js index 9c9f62d82..cdc138c8b 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js +++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js @@ -1,10 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Toggle from 'react-toggle'; import AsyncSelect from 'react-select/lib/Async'; +const messages = defineMessages({ + placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' }, + noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' }, +}); + export default @injectIntl class ColumnSettings extends React.PureComponent { @@ -25,6 +30,7 @@ class ColumnSettings extends React.PureComponent { tags (mode) { let tags = this.props.settings.getIn(['tags', mode]) || []; + if (tags.toJSON) { return tags.toJSON(); } else { @@ -32,33 +38,36 @@ class ColumnSettings extends React.PureComponent { } }; - onSelect = (mode) => { - return (value) => { - this.props.onChange(['tags', mode], value); - }; - }; + onSelect = mode => value => this.props.onChange(['tags', mode], value); onToggle = () => { if (this.state.open && this.hasTags()) { this.props.onChange('tags', {}); } + this.setState({ open: !this.state.open }); }; + noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions); + modeSelect (mode) { return ( - <div className='column-settings__section'> - {this.modeLabel(mode)} + <div className='column-settings__row'> + <span className='column-settings__section'> + {this.modeLabel(mode)} + </span> + <AsyncSelect isMulti autoFocus value={this.tags(mode)} - settings={this.props.settings} - settingPath={['tags', mode]} onChange={this.onSelect(mode)} loadOptions={this.props.onLoad} - classNamePrefix='column-settings__hashtag-select' + className='column-select__container' + classNamePrefix='column-select' name='tags' + placeholder={this.props.intl.formatMessage(messages.placeholder)} + noOptionsMessage={this.noOptionsMessage} /> </div> ); @@ -66,11 +75,15 @@ class ColumnSettings extends React.PureComponent { modeLabel (mode) { switch(mode) { - case 'any': return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />; - case 'all': return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />; - case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />; + case 'any': + return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />; + case 'all': + return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />; + case 'none': + return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />; + default: + return ''; } - return ''; }; render () { @@ -78,23 +91,21 @@ class ColumnSettings extends React.PureComponent { <div> <div className='column-settings__row'> <div className='setting-toggle'> - <Toggle - id='hashtag.column_settings.tag_toggle' - onChange={this.onToggle} - checked={this.state.open} - /> + <Toggle id='hashtag.column_settings.tag_toggle' onChange={this.onToggle} checked={this.state.open} /> + <span className='setting-toggle__label'> <FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' /> </span> </div> </div> - {this.state.open && + + {this.state.open && ( <div className='column-settings__hashtags'> {this.modeSelect('any')} {this.modeSelect('all')} {this.modeSelect('none')} </div> - } + )} </div> ); } diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js index c2e026d13..0d3c97a64 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/index.js +++ b/app/javascript/mastodon/features/hashtag_timeline/index.js @@ -41,15 +41,19 @@ class HashtagTimeline extends React.PureComponent { title = () => { let title = [this.props.params.id]; + if (this.additionalFor('any')) { - title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />); + title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />); } + if (this.additionalFor('all')) { - title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />); + title.push(' ', <FormattedMessage key='all' id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />); } + if (this.additionalFor('none')) { - title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />); + title.push(' ', <FormattedMessage key='none' id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />); } + return title; } @@ -77,9 +81,10 @@ class HashtagTimeline extends React.PureComponent { let all = (tags.all || []).map(tag => tag.value); let none = (tags.none || []).map(tag => tag.value); - [id, ...any].map((tag) => { - this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => { + [id, ...any].map(tag => { + this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => { let tags = status.tags.map(tag => tag.name); + return all.filter(tag => tags.includes(tag)).length === all.length && none.filter(tag => tags.includes(tag)).length === 0; }))); @@ -95,12 +100,14 @@ class HashtagTimeline extends React.PureComponent { const { dispatch } = this.props; const { id, tags } = this.props.params; + this._subscribe(dispatch, id, tags); dispatch(expandHashtagTimeline(id, { tags })); } componentWillReceiveProps (nextProps) { const { dispatch, params } = this.props; const { id, tags } = nextProps.params; + if (id !== params.id || !isEqual(tags, params.tags)) { this._unsubscribe(); this._subscribe(dispatch, id, tags); diff --git a/app/javascript/mastodon/features/list_editor/components/edit_list_form.js b/app/javascript/mastodon/features/list_editor/components/edit_list_form.js new file mode 100644 index 000000000..3dc59c12e --- /dev/null +++ b/app/javascript/mastodon/features/list_editor/components/edit_list_form.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { changeListEditorTitle, submitListEditor } from '../../../actions/lists'; +import IconButton from '../../../components/icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; + +const messages = defineMessages({ + title: { id: 'lists.edit.submit', defaultMessage: 'Change title' }, +}); + +const mapStateToProps = state => ({ + value: state.getIn(['listEditor', 'title']), + disabled: !state.getIn(['listEditor', 'isChanged']), +}); + +const mapDispatchToProps = dispatch => ({ + onChange: value => dispatch(changeListEditorTitle(value)), + onSubmit: () => dispatch(submitListEditor(false)), +}); + +export default @connect(mapStateToProps, mapDispatchToProps) +@injectIntl +class ListForm extends React.PureComponent { + + static propTypes = { + value: PropTypes.string.isRequired, + disabled: PropTypes.bool, + intl: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + }; + + handleChange = e => { + this.props.onChange(e.target.value); + } + + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + } + + handleClick = () => { + this.props.onSubmit(); + } + + render () { + const { value, disabled, intl } = this.props; + + const title = intl.formatMessage(messages.title); + + return ( + <form className='column-inline-form' onSubmit={this.handleSubmit}> + <input + className='setting-text' + value={value} + onChange={this.handleChange} + /> + + <IconButton + disabled={disabled} + icon='check' + title={title} + onClick={this.handleClick} + /> + </form> + ); + } + +} diff --git a/app/javascript/mastodon/features/list_editor/index.js b/app/javascript/mastodon/features/list_editor/index.js index aab0cdd0c..48466604a 100644 --- a/app/javascript/mastodon/features/list_editor/index.js +++ b/app/javascript/mastodon/features/list_editor/index.js @@ -7,11 +7,11 @@ import { injectIntl } from 'react-intl'; import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists'; import Account from './components/account'; import Search from './components/search'; +import EditListForm from './components/edit_list_form'; import Motion from '../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; const mapStateToProps = state => ({ - title: state.getIn(['listEditor', 'title']), accountIds: state.getIn(['listEditor', 'accounts', 'items']), searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']), }); @@ -33,7 +33,6 @@ class ListEditor extends ImmutablePureComponent { onInitialize: PropTypes.func.isRequired, onClear: PropTypes.func.isRequired, onReset: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, accountIds: ImmutablePropTypes.list.isRequired, searchAccountIds: ImmutablePropTypes.list.isRequired, }; @@ -49,12 +48,12 @@ class ListEditor extends ImmutablePureComponent { } render () { - const { title, accountIds, searchAccountIds, onClear } = this.props; + const { accountIds, searchAccountIds, onClear } = this.props; const showSearch = searchAccountIds.size > 0; return ( <div className='modal-root__modal list-editor'> - <h4>{title}</h4> + <EditListForm /> <Search /> diff --git a/app/javascript/mastodon/reducers/list_editor.js b/app/javascript/mastodon/reducers/list_editor.js index 02a0dabb1..91e524dd5 100644 --- a/app/javascript/mastodon/reducers/list_editor.js +++ b/app/javascript/mastodon/reducers/list_editor.js @@ -22,6 +22,7 @@ import { const initialState = ImmutableMap({ listId: null, isSubmitting: false, + isChanged: false, title: '', accounts: ImmutableMap({ @@ -47,10 +48,16 @@ export default function listEditorReducer(state = initialState, action) { map.set('isSubmitting', false); }); case LIST_EDITOR_TITLE_CHANGE: - return state.set('title', action.value); + return state.withMutations(map => { + map.set('title', action.value); + map.set('isChanged', true); + }); case LIST_CREATE_REQUEST: case LIST_UPDATE_REQUEST: - return state.set('isSubmitting', true); + return state.withMutations(map => { + map.set('isSubmitting', true); + map.set('isChanged', false); + }); case LIST_CREATE_FAIL: case LIST_UPDATE_FAIL: return state.set('isSubmitting', false); diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss index 7d8993a50..8429103b8 100644 --- a/app/javascript/styles/contrast/diff.scss +++ b/app/javascript/styles/contrast/diff.scss @@ -13,6 +13,10 @@ } } +.rich-formatting a, +.rich-formatting p a, +.rich-formatting li a, +.landing-page__short-description p a, .status__content a, .reply-indicator__content a { color: lighten($ui-highlight-color, 12%); diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 78bc2dbb6..de03cf1a6 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -352,6 +352,8 @@ .moved-account-widget, .memoriam-widget, .activity-stream, -.nothing-here { +.nothing-here, +.directory__tag > a, +.directory__tag > div { box-shadow: none; } diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index d5bafe6b6..08806599e 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -41,3 +41,34 @@ font-size: 16px; } } + +@mixin search-popout() { + background: $simple-background-color; + border-radius: 4px; + padding: 10px 14px; + padding-bottom: 14px; + margin-top: 10px; + color: $light-text-color; + box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); + + h4 { + text-transform: uppercase; + color: $light-text-color; + font-size: 13px; + font-weight: 500; + margin-bottom: 10px; + } + + li { + padding: 4px 0; + } + + ul { + margin-bottom: 10px; + } + + em { + font-weight: 500; + color: $inverted-text-color; + } +} diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index b6c92a09e..b078d4d24 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -49,15 +49,9 @@ $small-breakpoint: 960px; } } + strong, em { - display: inline; - margin: 0; - padding: 0; font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; color: lighten($darker-text-color, 10%); } @@ -796,7 +790,7 @@ $small-breakpoint: 960px; width: 100%; display: flex; flex-direction: row-reverse; - flex-wrap: wrap; + flex-wrap: nowrap; justify-content: space-between; align-items: center; } @@ -846,14 +840,7 @@ $small-breakpoint: 960px; } strong { - display: inline; - margin: 0; - padding: 0; - font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; + font-weight: 500; color: lighten($darker-text-color, 10%); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index e29abf4f3..11823a45b 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -476,7 +476,7 @@ opacity: 0; transition: opacity .1s ease; - input { + textarea { background: transparent; color: $secondary-text-color; border: 0; @@ -3056,14 +3056,41 @@ a.status-card.compact:hover { display: block; font-weight: 500; margin-bottom: 10px; +} + +.column-settings__hashtags { + .column-settings__row { + margin-bottom: 15px; + } - .column-settings__hashtag-select { + .column-select { &__control { @include search-input(); } + &__placeholder { + color: $dark-text-color; + padding-left: 2px; + font-size: 12px; + } + + &__value-container { + padding-left: 6px; + } + &__multi-value { background: lighten($ui-base-color, 8%); + + &__remove { + cursor: pointer; + + &:hover, + &:active, + &:focus { + background: lighten($ui-base-color, 12%); + color: lighten($darker-text-color, 4%); + } + } } &__multi-value__label, @@ -3071,9 +3098,42 @@ a.status-card.compact:hover { color: $darker-text-color; } - &__indicator-separator, + &__clear-indicator, &__dropdown-indicator { - display: none; + cursor: pointer; + transition: none; + color: $dark-text-color; + + &:hover, + &:active, + &:focus { + color: lighten($dark-text-color, 4%); + } + } + + &__indicator-separator { + background-color: lighten($ui-base-color, 8%); + } + + &__menu { + @include search-popout(); + padding: 0; + background: $ui-secondary-color; + } + + &__menu-list { + padding: 6px; + } + + &__option { + color: $inverted-text-color; + border-radius: 4px; + font-size: 14px; + + &--is-focused, + &--is-selected { + background: darken($ui-secondary-color, 10%); + } } } } @@ -4867,34 +4927,7 @@ a.status-card.compact:hover { } .search-popout { - background: $simple-background-color; - border-radius: 4px; - padding: 10px 14px; - padding-bottom: 14px; - margin-top: 10px; - color: $light-text-color; - box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); - - h4 { - text-transform: uppercase; - color: $light-text-color; - font-size: 13px; - font-weight: 500; - margin-bottom: 10px; - } - - li { - padding: 4px 0; - } - - ul { - margin-bottom: 10px; - } - - em { - font-weight: 500; - color: $inverted-text-color; - } + @include search-popout(); } noscript { @@ -5130,7 +5163,7 @@ noscript { .icon-button { flex: 0 0 auto; - margin-left: 5px; + margin: 0 5px; } } |