From 5770d461b21cf5b6a8adcaa44d19832e11289960 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Thu, 13 Jul 2017 02:40:16 -0700 Subject: Moved glitch containers and commented unused files --- .../glitch/components/status/container.js | 252 +++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 app/javascript/glitch/components/status/container.js (limited to 'app/javascript/glitch/components/status/container.js') diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js new file mode 100644 index 000000000..a8aa6efe9 --- /dev/null +++ b/app/javascript/glitch/components/status/container.js @@ -0,0 +1,252 @@ +/* + +`` +=================== + +Original file by @gargron@mastodon.social et al as part of +tootsuite/mastodon. Documentation by @kibi@glitch.social. The code +detecting reblogs has been moved here from . + +*/ + + /* * * * */ + +/* + +Imports: +-------- + +*/ + +// Package imports // +import React from 'react'; +import { connect } from 'react-redux'; +import { + defineMessages, + injectIntl, + FormattedMessage, +} from 'react-intl'; + +// Mastodon imports // +import { makeGetStatus } from '../../../mastodon/selectors'; +import { + replyCompose, + mentionCompose, +} from '../../../mastodon/actions/compose'; +import { + reblog, + favourite, + unreblog, + unfavourite, +} from '../../../mastodon/actions/interactions'; +import { + blockAccount, + muteAccount, +} from '../../../mastodon/actions/accounts'; +import { + muteStatus, + unmuteStatus, + deleteStatus, +} from '../../../mastodon/actions/statuses'; +import { initReport } from '../../../mastodon/actions/reports'; +import { openModal } from '../../../mastodon/actions/modal'; + +// Our imports // +import Status from '.'; + + /* * * * */ + +/* + +Inital setup: +------------- + +The `messages` constant is used to define any messages that we will +need in our component. In our case, these are the various confirmation +messages used with statuses. + +*/ + +const messages = defineMessages({ + deleteConfirm : { + id : 'confirmations.delete.confirm', + defaultMessage : 'Delete', + }, + deleteMessage : { + id : 'confirmations.delete.message', + defaultMessage : 'Are you sure you want to delete this status?', + }, + blockConfirm : { + id : 'confirmations.block.confirm', + defaultMessage : 'Block', + }, + muteConfirm : { + id : 'confirmations.mute.confirm', + defaultMessage : 'Mute', + }, +}); + + /* * * * */ + +/* + +State mapping: +-------------- + +The `mapStateToProps()` function maps various state properties to the +props of our component. We wrap this in a `makeMapStateToProps()` +function to give us closure and preserve `getStatus()` across function +calls. + +*/ + +const makeMapStateToProps = () => { + const getStatus = makeGetStatus(); + + const mapStateToProps = (state, ownProps) => { + + let status = getStatus(state, ownProps.id); + let reblogStatus = status.get('reblog', null); + let account = undefined; + let prepend = undefined; + +/* + +Here we process reblogs. If our status is a reblog, then we create a +`prependMessage` to pass along to our `` along with the +reblogger's `account`, and set `coreStatus` (the one we will actually +render) to the status which has been reblogged. + +*/ + + if (reblogStatus !== null && typeof reblogStatus === 'object') { + account = status.get('account'); + status = reblogStatus; + prepend = 'reblogged_by'; + } + +/* + +Here are the props we pass to ``. + +*/ + + return { + status : status, + account : account || ownProps.account, + me : state.getIn(['meta', 'me']), + settings : state.get('local_settings'), + prepend : prepend || ownProps.prepend, + reblogModal : state.getIn(['meta', 'boost_modal']), + deleteModal : state.getIn(['meta', 'delete_modal']), + autoPlayGif : state.getIn(['meta', 'auto_play_gif']), + }; + }; + + return mapStateToProps; +}; + + /* * * * */ + +/* + +Dispatch mapping: +----------------- + +The `mapDispatchToProps()` function maps dispatches to our store to the +various props of our component. We need to provide dispatches for all +of the things you can do with a status: reply, reblog, favourite, et +cetera. + +For a few of these dispatches, we open up confirmation modals; the rest +just immediately execute their corresponding actions. + +*/ + +const mapDispatchToProps = (dispatch, { intl }) => ({ + + onReply (status, router) { + dispatch(replyCompose(status, router)); + }, + + onModalReblog (status) { + dispatch(reblog(status)); + }, + + onReblog (status, e) { + if (status.get('reblogged')) { + dispatch(unreblog(status)); + } else { + if (e.shiftKey || !this.reblogModal) { + this.onModalReblog(status); + } else { + dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); + } + } + }, + + onFavourite (status) { + if (status.get('favourited')) { + dispatch(unfavourite(status)); + } else { + dispatch(favourite(status)); + } + }, + + onDelete (status) { + if (!this.deleteModal) { + dispatch(deleteStatus(status.get('id'))); + } else { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.deleteMessage), + confirm: intl.formatMessage(messages.deleteConfirm), + onConfirm: () => dispatch(deleteStatus(status.get('id'))), + })); + } + }, + + onMention (account, router) { + dispatch(mentionCompose(account, router)); + }, + + onOpenMedia (media, index) { + dispatch(openModal('MEDIA', { media, index })); + }, + + onOpenVideo (media, time) { + dispatch(openModal('VIDEO', { media, time })); + }, + + onBlock (account) { + dispatch(openModal('CONFIRM', { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.blockConfirm), + onConfirm: () => dispatch(blockAccount(account.get('id'))), + })); + }, + + onReport (status) { + dispatch(initReport(status.get('account'), status)); + }, + + onMute (account) { + dispatch(openModal('CONFIRM', { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.muteConfirm), + onConfirm: () => dispatch(muteAccount(account.get('id'))), + })); + }, + + onMuteConversation (status) { + if (status.get('muted')) { + dispatch(unmuteStatus(status.get('id'))); + } else { + dispatch(muteStatus(status.get('id'))); + } + }, + +}); + +export default injectIntl( + connect(makeMapStateToProps, mapDispatchToProps)(Status) +); -- cgit From 75aafc932e42b5dba1030700bb47be0db41b1ab5 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Fri, 14 Jul 2017 17:03:43 +0200 Subject: Added buttons and menu items to dismiss individual notifications (#76) * Added DELETE verb for notifications * Added notification dismiss button to status dropdown * Added reveal-on-hover notif dismiss button, added FollowNotification component --- app/controllers/api/v1/notifications_controller.rb | 4 ++ .../glitch/components/notification/container.js | 9 ++- .../components/notification/follow_notification.js | 78 ++++++++++++++++++++++ .../glitch/components/notification/index.js | 31 +++------ .../glitch/components/status/action_bar.js | 8 +++ .../glitch/components/status/container.js | 4 ++ app/javascript/glitch/components/status/index.js | 4 ++ app/javascript/glitch/components/status/prepend.js | 29 +++++++- app/javascript/mastodon/actions/notifications.js | 17 +++++ app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/reducers/notifications.js | 7 ++ app/javascript/styles/components.scss | 21 ++++++ config/routes.rb | 2 +- 13 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 app/javascript/glitch/components/notification/follow_notification.js (limited to 'app/javascript/glitch/components/status/container.js') diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 8910b77e9..55f35fa4b 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -24,6 +24,10 @@ class Api::V1::NotificationsController < Api::BaseController render_empty end + def destroy + dismiss + end + def dismiss current_account.notifications.find_by!(id: params[:id]).destroy! render_empty diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js index c58ef4bd2..60303537d 100644 --- a/app/javascript/glitch/components/notification/container.js +++ b/app/javascript/glitch/components/notification/container.js @@ -6,6 +6,7 @@ import { makeGetNotification } from '../../../mastodon/selectors'; // Our imports // import Notification from '.'; +import { deleteNotification } from '../../../mastodon/actions/notifications'; const makeMapStateToProps = () => { const getNotification = makeGetNotification(); @@ -18,4 +19,10 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -export default connect(makeMapStateToProps)(Notification); +const mapDispatchToProps = (dispatch) => ({ + onDeleteNotification (id) { + dispatch(deleteNotification(id)); + }, +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(Notification); diff --git a/app/javascript/glitch/components/notification/follow_notification.js b/app/javascript/glitch/components/notification/follow_notification.js new file mode 100644 index 000000000..7cabd91f6 --- /dev/null +++ b/app/javascript/glitch/components/notification/follow_notification.js @@ -0,0 +1,78 @@ +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import escapeTextContentForBrowser from 'escape-html'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // +import emojify from '../../../mastodon/emoji'; +import Permalink from '../../../mastodon/components/permalink'; +import AccountContainer from '../../../mastodon/containers/account_container'; + +const messages = defineMessages({ + deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, +}); + + +@injectIntl +export default class FollowNotification extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + notificationId: PropTypes.number.isRequired, + onDeleteNotification: PropTypes.func.isRequired, + account: ImmutablePropTypes.map.isRequired, + intl: PropTypes.object.isRequired, + }; + + // Avoid checking props that are functions (and whose equality will always + // evaluate to false. See react-immutable-pure-component for usage. + updateOnProps = [ + 'account', + ] + + handleNotificationDeleteClick = () => { + this.props.onDeleteNotification(this.props.notificationId); + } + + render () { + const { account, intl } = this.props; + + const dismissTitle = intl.formatMessage(messages.deleteNotification); + const dismiss = ( + + ); + + const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); + const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; + const link = ; + return ( +
+
+
+ +
+ + + + {dismiss} +
+ + +
+ ); + } + +} diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js index 83ac8dfc1..0cdc03cbe 100644 --- a/app/javascript/glitch/components/notification/index.js +++ b/app/javascript/glitch/components/notification/index.js @@ -1,42 +1,30 @@ // Package imports // import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage } from 'react-intl'; -import escapeTextContentForBrowser from 'escape-html'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; // Mastodon imports // -import AccountContainer from '../../../mastodon/containers/account_container'; -import Permalink from '../../../mastodon/components/permalink'; -import emojify from '../../../mastodon/emoji'; // Our imports // import StatusContainer from '../status/container'; +import FollowNotification from './follow_notification'; export default class Notification extends ImmutablePureComponent { static propTypes = { notification: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired, + onDeleteNotification: PropTypes.func.isRequired, }; renderFollow (notification) { - const account = notification.get('account'); - const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - const link = ; return ( -
-
-
- -
- - -
- - -
+ ); } @@ -44,6 +32,7 @@ export default class Notification extends ImmutablePureComponent { return ( ); @@ -56,6 +45,7 @@ export default class Notification extends ImmutablePureComponent { account={notification.get('account')} prepend='favourite' muted + notificationId={notification.get('id')} withDismiss /> ); @@ -68,6 +58,7 @@ export default class Notification extends ImmutablePureComponent { account={notification.get('account')} prepend='reblog' muted + notificationId={notification.get('id')} withDismiss /> ); diff --git a/app/javascript/glitch/components/status/action_bar.js b/app/javascript/glitch/components/status/action_bar.js index f298dcaa8..6aa088c04 100644 --- a/app/javascript/glitch/components/status/action_bar.js +++ b/app/javascript/glitch/components/status/action_bar.js @@ -24,6 +24,7 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, + deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, }); @injectIntl @@ -35,6 +36,7 @@ export default class StatusActionBar extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, + notificationId: PropTypes.number, onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, @@ -44,6 +46,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onBlock: PropTypes.func, onReport: PropTypes.func, onMuteConversation: PropTypes.func, + onDeleteNotification: PropTypes.func, me: PropTypes.number.isRequired, withDismiss: PropTypes.bool, intl: PropTypes.object.isRequired, @@ -97,6 +100,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onMuteConversation(this.props.status); } + handleNotificationDeleteClick = () => { + this.props.onDeleteNotification(this.props.notificationId); + } + render () { const { status, me, intl, withDismiss } = this.props; const reblogDisabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct'; @@ -112,6 +119,7 @@ export default class StatusActionBar extends ImmutablePureComponent { if (withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); + menu.push({ text: intl.formatMessage(messages.deleteNotification), action: this.handleNotificationDeleteClick }); menu.push(null); } diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js index a8aa6efe9..c45b2e0ec 100644 --- a/app/javascript/glitch/components/status/container.js +++ b/app/javascript/glitch/components/status/container.js @@ -50,6 +50,7 @@ import { } from '../../../mastodon/actions/statuses'; import { initReport } from '../../../mastodon/actions/reports'; import { openModal } from '../../../mastodon/actions/modal'; +import { deleteNotification } from '../../../mastodon/actions/notifications'; // Our imports // import Status from '.'; @@ -245,6 +246,9 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onDeleteNotification (id) { + dispatch(deleteNotification(id)); + }, }); export default injectIntl( diff --git a/app/javascript/glitch/components/status/index.js b/app/javascript/glitch/components/status/index.js index 1d135754a..314e8b51c 100644 --- a/app/javascript/glitch/components/status/index.js +++ b/app/javascript/glitch/components/status/index.js @@ -170,6 +170,7 @@ export default class Status extends ImmutablePureComponent { onReport : PropTypes.func, onOpenMedia : PropTypes.func, onOpenVideo : PropTypes.func, + onDeleteNotification : PropTypes.func, reblogModal : PropTypes.bool, deleteModal : PropTypes.bool, autoPlayGif : PropTypes.bool, @@ -177,6 +178,7 @@ export default class Status extends ImmutablePureComponent { collapse : PropTypes.bool, prepend : PropTypes.string, withDismiss : PropTypes.bool, + notificationId : PropTypes.number, intersectionObserverWrapper : PropTypes.object, }; @@ -685,6 +687,8 @@ collapsed. type={prepend} account={account} parseClick={parseClick} + notificationId={this.props.notificationId} + onDeleteNotification={this.props.onDeleteNotification} /> ) : null} { + this.props.onDeleteNotification(this.props.notificationId); + } + /* #### ``. @@ -145,7 +159,19 @@ the `` inside of an