diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features')
5 files changed, 212 insertions, 65 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/account_note.js b/app/javascript/flavours/glitch/features/account/components/account_note.js index e7fd4c5ff..8a99bfcb8 100644 --- a/app/javascript/flavours/glitch/features/account/components/account_note.js +++ b/app/javascript/flavours/glitch/features/account/components/account_note.js @@ -7,7 +7,7 @@ import Icon from 'flavours/glitch/components/icon'; import Textarea from 'react-textarea-autosize'; const messages = defineMessages({ - placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' }, + placeholder: { id: 'account_note.glitch_placeholder', defaultMessage: 'No comment provided' }, }); export default @injectIntl @@ -63,6 +63,14 @@ class Header extends ImmutablePureComponent { </button> </div> ); + } else { + action_buttons = ( + <div className='account__header__account-note__buttons'> + <button className='text-btn' tabIndex='0' onClick={this.props.onEditAccountNote} disabled={isSubmitting}> + <Icon id='pencil' size={15} /> <FormattedMessage id='account_note.edit' defaultMessage='Edit' /> + </button> + </div> + ); } let note_container = null; @@ -85,17 +93,10 @@ class Header extends ImmutablePureComponent { return ( <div className='account__header__account-note'> <div className='account__header__account-note__header'> - <strong><FormattedMessage id='account.account_note_header' defaultMessage='Your note for @{name}' values={{ name: account.get('username') }} /></strong> - {!isEditing && ( - <div> - <button className='text-btn' tabIndex='0' onClick={this.props.onEditAccountNote} disabled={isSubmitting}> - <Icon id='pencil' size={15} /> <FormattedMessage id='account_note.edit' defaultMessage='Edit' /> - </button> - </div> - )} + <strong><FormattedMessage id='account.account_note_header' defaultMessage='Note' /></strong> + {action_buttons} </div> {note_container} - {action_buttons} </div> ); } diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index a5abf38ae..572f34fa0 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -9,7 +9,6 @@ import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; import Avatar from 'flavours/glitch/components/avatar'; import Button from 'flavours/glitch/components/button'; -import { shortNumberFormat } from 'flavours/glitch/util/numbers'; import { NavLink } from 'react-router-dom'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import AccountNoteContainer from '../containers/account_note_container'; @@ -177,7 +176,7 @@ class Header extends ImmutablePureComponent { menu.push(null); } - if (accountNote === null) { + if (accountNote === null || accountNote === '') { menu.push({ text: intl.formatMessage(messages.add_account_note, { name: account.get('username') }), action: this.props.onEditAccountNote }); } diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js index 557120960..2ef9d5ba4 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.js +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js @@ -11,8 +11,14 @@ import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import IconButton from 'flavours/glitch/components/icon_button'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/util/initial_state'; -import { shortNumberFormat } from 'flavours/glitch/util/numbers'; -import { followAccount, unfollowAccount, blockAccount, unblockAccount, unmuteAccount } from 'flavours/glitch/actions/accounts'; +import ShortNumber from 'flavours/glitch/components/short_number'; +import { + followAccount, + unfollowAccount, + blockAccount, + unblockAccount, + unmuteAccount, +} from 'flavours/glitch/actions/accounts'; import { openModal } from 'flavours/glitch/actions/modal'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; @@ -22,7 +28,10 @@ const messages = defineMessages({ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, - unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, + unfollowConfirm: { + id: 'confirmations.unfollow.confirm', + defaultMessage: 'Unfollow', + }, }); const makeMapStateToProps = () => { @@ -36,15 +45,25 @@ const makeMapStateToProps = () => { }; const mapDispatchToProps = (dispatch, { intl }) => ({ - - onFollow (account) { - if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { + onFollow(account) { + if ( + account.getIn(['relationship', 'following']) || + account.getIn(['relationship', 'requested']) + ) { if (unfollowModal) { - dispatch(openModal('CONFIRM', { - message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - })); + dispatch( + openModal('CONFIRM', { + message: ( + <FormattedMessage + id='confirmations.unfollow.message' + defaultMessage='Are you sure you want to unfollow {name}?' + values={{ name: <strong>@{account.get('acct')}</strong> }} + /> + ), + confirm: intl.formatMessage(messages.unfollowConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + }), + ); } else { dispatch(unfollowAccount(account.get('id'))); } @@ -53,7 +72,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - onBlock (account) { + onBlock(account) { if (account.getIn(['relationship', 'blocking'])) { dispatch(unblockAccount(account.get('id'))); } else { @@ -61,17 +80,17 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - onMute (account) { + onMute(account) { if (account.getIn(['relationship', 'muting'])) { dispatch(unmuteAccount(account.get('id'))); } else { dispatch(initMuteModal(account)); } }, - }); -export default @injectIntl +export default +@injectIntl @connect(makeMapStateToProps, mapDispatchToProps) class AccountCard extends ImmutablePureComponent { @@ -83,7 +102,7 @@ class AccountCard extends ImmutablePureComponent { onMute: PropTypes.func.isRequired, }; - _updateEmojis () { + _updateEmojis() { const node = this.node; if (!node || autoPlayGif) { @@ -104,68 +123,113 @@ class AccountCard extends ImmutablePureComponent { } } - componentDidMount () { + componentDidMount() { this._updateEmojis(); } - componentDidUpdate () { + componentDidUpdate() { this._updateEmojis(); } handleEmojiMouseEnter = ({ target }) => { target.src = target.getAttribute('data-original'); - } + }; handleEmojiMouseLeave = ({ target }) => { target.src = target.getAttribute('data-static'); - } + }; handleFollow = () => { this.props.onFollow(this.props.account); - } + }; handleBlock = () => { this.props.onBlock(this.props.account); - } + }; handleMute = () => { this.props.onMute(this.props.account); - } + }; setRef = (c) => { this.node = c; - } + }; - render () { + render() { const { account, intl } = this.props; let buttons; - if (account.get('id') !== me && account.get('relationship', null) !== null) { + if ( + account.get('id') !== me && + account.get('relationship', null) !== null + ) { const following = account.getIn(['relationship', 'following']); const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); + const blocking = account.getIn(['relationship', 'blocking']); + const muting = account.getIn(['relationship', 'muting']); if (requested) { - buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />; + buttons = ( + <IconButton + disabled + icon='hourglass' + title={intl.formatMessage(messages.requested)} + /> + ); } else if (blocking) { - buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />; + buttons = ( + <IconButton + active + icon='unlock' + title={intl.formatMessage(messages.unblock, { + name: account.get('username'), + })} + onClick={this.handleBlock} + /> + ); } else if (muting) { - buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />; + buttons = ( + <IconButton + active + icon='volume-up' + title={intl.formatMessage(messages.unmute, { + name: account.get('username'), + })} + onClick={this.handleMute} + /> + ); } else if (!account.get('moved') || following) { - buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />; + buttons = ( + <IconButton + icon={following ? 'user-times' : 'user-plus'} + title={intl.formatMessage( + following ? messages.unfollow : messages.follow, + )} + onClick={this.handleFollow} + active={following} + /> + ); } } return ( <div className='directory__card'> <div className='directory__card__img'> - <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' /> + <img + src={ + autoPlayGif ? account.get('header') : account.get('header_static') + } + alt='' + /> </div> <div className='directory__card__bar'> - <Permalink className='directory__card__bar__name' href={account.get('url')} to={`/accounts/${account.get('id')}`}> + <Permalink + className='directory__card__bar__name' + href={account.get('url')} + to={`/accounts/${account.get('id')}`} + > <Avatar account={account} size={48} /> <DisplayName account={account} /> </Permalink> @@ -176,13 +240,44 @@ class AccountCard extends ImmutablePureComponent { </div> <div className='directory__card__extra' ref={this.setRef}> - <div className='account__header__content' dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} /> + <div + className='account__header__content' + dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} + /> </div> <div className='directory__card__extra'> - <div className='accounts-table__count'>{shortNumberFormat(account.get('statuses_count'))} <small><FormattedMessage id='account.posts' defaultMessage='Toots' /></small></div> - <div className='accounts-table__count'>{account.get('followers_count') < 0 ? '-' : shortNumberFormat(account.get('followers_count'))} <small><FormattedMessage id='account.followers' defaultMessage='Followers' /></small></div> - <div className='accounts-table__count'>{account.get('last_status_at') === null ? <FormattedMessage id='account.never_active' defaultMessage='Never' /> : <RelativeTimestamp timestamp={account.get('last_status_at')} />} <small><FormattedMessage id='account.last_status' defaultMessage='Last active' /></small></div> + <div className='accounts-table__count'> + <ShortNumber value={account.get('statuses_count')} /> + <small> + <FormattedMessage id='account.posts' defaultMessage='Toots' /> + </small> + </div> + <div className='accounts-table__count'> + {account.get('followers_count') < 0 ? '-' : <ShortNumber value={account.get('followers_count')} />}{' '} + <small> + <FormattedMessage + id='account.followers' + defaultMessage='Followers' + /> + </small> + </div> + <div className='accounts-table__count'> + {account.get('last_status_at') === null ? ( + <FormattedMessage + id='account.never_active' + defaultMessage='Never' + /> + ) : ( + <RelativeTimestamp timestamp={account.get('last_status_at')} /> + )}{' '} + <small> + <FormattedMessage + id='account.last_status' + defaultMessage='Last active' + /> + </small> + </div> </div> </div> ); diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js index 956f16734..27300f020 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js @@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Toggle from 'react-toggle'; import AsyncSelect from 'react-select/async'; +import { NonceProvider } from 'react-select'; import SettingToggle from '../../notifications/components/setting_toggle'; const messages = defineMessages({ @@ -58,18 +59,20 @@ class ColumnSettings extends React.PureComponent { {this.modeLabel(mode)} </span> - <AsyncSelect - isMulti - autoFocus - value={this.tags(mode)} - onChange={this.onSelect(mode)} - loadOptions={this.props.onLoad} - className='column-select__container' - classNamePrefix='column-select' - name='tags' - placeholder={this.props.intl.formatMessage(messages.placeholder)} - noOptionsMessage={this.noOptionsMessage} - /> + <NonceProvider nonce={document.querySelector('meta[name=style-nonce]').content}> + <AsyncSelect + isMulti + autoFocus + value={this.tags(mode)} + onChange={this.onSelect(mode)} + loadOptions={this.props.onLoad} + className='column-select__container' + classNamePrefix='column-select' + name='tags' + placeholder={this.props.intl.formatMessage(messages.placeholder)} + noOptionsMessage={this.noOptionsMessage} + /> + </NonceProvider> </div> ); } diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js index 2f4200e10..c8b0e4bd7 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import classNames from 'classnames'; -import { changeUploadCompose } from 'flavours/glitch/actions/compose'; +import { changeUploadCompose, uploadThumbnail } from 'flavours/glitch/actions/compose'; import { getPointerPosition } from 'flavours/glitch/features/video'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import IconButton from 'flavours/glitch/components/icon_button'; @@ -23,11 +23,13 @@ const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' }, placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' }, + chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' }, }); const mapStateToProps = (state, { id }) => ({ media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), account: state.getIn(['accounts', me]), + isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']), }); const mapDispatchToProps = (dispatch, { id }) => ({ @@ -36,6 +38,10 @@ const mapDispatchToProps = (dispatch, { id }) => ({ dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` })); }, + onSelectThumbnail: files => { + dispatch(uploadThumbnail(id, files[0])); + }, + }); const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******') @@ -81,6 +87,9 @@ class FocalPointModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired, + isUploadingThumbnail: PropTypes.bool, + onSave: PropTypes.func.isRequired, + onSelectThumbnail: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -235,13 +244,29 @@ class FocalPointModal extends ImmutablePureComponent { }).catch(() => this.setState({ detecting: false })); } + handleThumbnailChange = e => { + if (e.target.files.length > 0) { + this.setState({ dirty: true }); + this.props.onSelectThumbnail(e.target.files); + } + } + + setFileInputRef = c => { + this.fileInput = c; + } + + handleFileInputClick = () => { + this.fileInput.click(); + } + render () { - const { media, intl, account, onClose } = this.props; + const { media, intl, account, onClose, isUploadingThumbnail } = this.props; const { x, y, dragging, description, dirty, detecting, progress } = this.state; const width = media.getIn(['meta', 'original', 'width']) || null; const height = media.getIn(['meta', 'original', 'height']) || null; const focals = ['image', 'gifv'].includes(media.get('type')); + const thumbnailable = ['audio', 'video'].includes(media.get('type')); const previewRatio = 16/9; const previewWidth = 200; @@ -268,6 +293,30 @@ class FocalPointModal extends ImmutablePureComponent { <div className='report-modal__comment'> {focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>} + {thumbnailable && ( + <React.Fragment> + <label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label> + + <Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} /> + + <label> + <span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span> + + <input + id='upload-modal__thumbnail' + ref={this.setFileInputRef} + type='file' + accept='image/png,image/jpeg' + onChange={this.handleThumbnailChange} + style={{ display: 'none' }} + disabled={isUploadingThumbnail} + /> + </label> + + <hr className='setting-divider' /> + </React.Fragment> + )} + <label className='setting-text-label' htmlFor='upload-modal__description'> {descriptionLabel} </label> @@ -293,7 +342,7 @@ class FocalPointModal extends ImmutablePureComponent { <CharacterCounter max={1500} text={detecting ? '' : description} /> </div> - <Button disabled={!dirty || detecting || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> + <Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> </div> <div className='focal-point-modal__content'> |