diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features')
5 files changed, 159 insertions, 0 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 new file mode 100644 index 000000000..e7fd4c5ff --- /dev/null +++ b/app/javascript/flavours/glitch/features/account/components/account_note.js @@ -0,0 +1,103 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +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' }, +}); + +export default @injectIntl +class Header extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + isEditing: PropTypes.bool, + isSubmitting: PropTypes.bool, + accountNote: PropTypes.string, + onEditAccountNote: PropTypes.func.isRequired, + onCancelAccountNote: PropTypes.func.isRequired, + onSaveAccountNote: PropTypes.func.isRequired, + onChangeAccountNote: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleChangeAccountNote = (e) => { + this.props.onChangeAccountNote(e.target.value); + }; + + componentWillUnmount () { + if (this.props.isEditing) { + this.props.onCancelAccountNote(); + } + } + + handleKeyDown = e => { + if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { + this.props.onSaveAccountNote(); + } else if (e.keyCode === 27) { + this.props.onCancelAccountNote(); + } + } + + render () { + const { account, accountNote, isEditing, isSubmitting, intl } = this.props; + + if (!account || (!accountNote && !isEditing)) { + return null; + } + + let action_buttons = null; + if (isEditing) { + action_buttons = ( + <div className='account__header__account-note__buttons'> + <button className='text-btn' tabIndex='0' onClick={this.props.onCancelAccountNote} disabled={isSubmitting}> + <Icon id='times' size={15} /> <FormattedMessage id='account_note.cancel' defaultMessage='Cancel' /> + </button> + <div className='flex-spacer' /> + <button className='text-btn' tabIndex='0' onClick={this.props.onSaveAccountNote} disabled={isSubmitting}> + <Icon id='check' size={15} /> <FormattedMessage id='account_note.save' defaultMessage='Save' /> + </button> + </div> + ); + } + + let note_container = null; + if (isEditing) { + note_container = ( + <Textarea + className='account__header__account-note__content' + disabled={isSubmitting} + placeholder={intl.formatMessage(messages.placeholder)} + value={accountNote} + onChange={this.handleChangeAccountNote} + onKeyDown={this.handleKeyDown} + autoFocus + /> + ); + } else { + note_container = (<div className='account__header__account-note__content'>{accountNote}</div>); + } + + 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> + )} + </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 c7b54649c..a5abf38ae 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -12,6 +12,7 @@ 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'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -46,6 +47,7 @@ const messages = defineMessages({ unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' }, add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, + add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' }, }); const dateFormatOptions = { @@ -65,6 +67,7 @@ class Header extends ImmutablePureComponent { identity_props: ImmutablePropTypes.list, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, + onEditAccountNote: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, domain: PropTypes.string.isRequired, }; @@ -121,6 +124,8 @@ class Header extends ImmutablePureComponent { return null; } + const accountNote = account.getIn(['relationship', 'note']); + let info = []; let actionBtn = ''; let lockedIcon = ''; @@ -172,6 +177,10 @@ class Header extends ImmutablePureComponent { menu.push(null); } + if (accountNote === null) { + menu.push({ text: intl.formatMessage(messages.add_account_note, { name: account.get('username') }), action: this.props.onEditAccountNote }); + } + if (account.get('id') === me) { if (profileLink) menu.push({ text: intl.formatMessage(messages.edit_profile), href: profileLink }); if (preferencesLink) menu.push({ text: intl.formatMessage(messages.preferences), href: preferencesLink }); @@ -278,6 +287,8 @@ class Header extends ImmutablePureComponent { </h1> </div> + <AccountNoteContainer account={account} /> + <div className='account__header__extra'> <div className='account__header__bio'> { (fields.size > 0 || identity_proofs.size > 0) && ( diff --git a/app/javascript/flavours/glitch/features/account/containers/account_note_container.js b/app/javascript/flavours/glitch/features/account/containers/account_note_container.js new file mode 100644 index 000000000..f1d007ecb --- /dev/null +++ b/app/javascript/flavours/glitch/features/account/containers/account_note_container.js @@ -0,0 +1,34 @@ +import { connect } from 'react-redux'; +import { changeAccountNoteComment, submitAccountNote, initEditAccountNote, cancelAccountNote } from 'flavours/glitch/actions/account_notes'; +import AccountNote from '../components/account_note'; + +const mapStateToProps = (state, { account }) => { + const isEditing = state.getIn(['account_notes', 'edit', 'account_id']) === account.get('id'); + + return { + isSubmitting: state.getIn(['account_notes', 'edit', 'isSubmitting']), + accountNote: isEditing ? state.getIn(['account_notes', 'edit', 'comment']) : account.getIn(['relationship', 'note']), + isEditing, + }; +}; + +const mapDispatchToProps = (dispatch, { account }) => ({ + + onEditAccountNote() { + dispatch(initEditAccountNote(account)); + }, + + onSaveAccountNote() { + dispatch(submitAccountNote()); + }, + + onCancelAccountNote() { + dispatch(cancelAccountNote()); + }, + + onChangeAccountNote(comment) { + dispatch(changeAccountNoteComment(comment)); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AccountNote); diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index 0faa8a424..1bab05c72 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -24,6 +24,7 @@ export default class Header extends ImmutablePureComponent { onUnblockDomain: PropTypes.func.isRequired, onEndorseToggle: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired, + onEditAccountNote: PropTypes.func.isRequired, hideTabs: PropTypes.bool, domain: PropTypes.string.isRequired, }; @@ -84,6 +85,10 @@ export default class Header extends ImmutablePureComponent { this.props.onAddToList(this.props.account); } + handleEditAccountNote = () => { + this.props.onEditAccountNote(this.props.account); + } + render () { const { account, hideTabs, identity_proofs } = this.props; @@ -109,6 +114,7 @@ export default class Header extends ImmutablePureComponent { onUnblockDomain={this.handleUnblockDomain} onEndorseToggle={this.handleEndorseToggle} onAddToList={this.handleAddToList} + onEditAccountNote={this.handleEditAccountNote} domain={this.props.domain} /> diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js index fff5e097f..225910292 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js @@ -19,6 +19,7 @@ import { initBlockModal } from 'flavours/glitch/actions/blocks'; import { initReport } from 'flavours/glitch/actions/reports'; import { openModal } from 'flavours/glitch/actions/modal'; import { blockDomain, unblockDomain } from 'flavours/glitch/actions/domain_blocks'; +import { initEditAccountNote } from 'flavours/glitch/actions/account_notes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { unfollowModal } from 'flavours/glitch/util/initial_state'; import { List as ImmutableList } from 'immutable'; @@ -106,6 +107,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onEditAccountNote (account) { + dispatch(initEditAccountNote(account)); + }, + onBlockDomain (domain) { dispatch(openModal('CONFIRM', { message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />, |