diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index 5b1163b3ab..d9d7e14060 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -207,7 +207,9 @@ export function emojiReact(status, emoji) { return function (dispatch, getState) { dispatch(emojiReactRequest(status, emoji)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/emoji_reactions`, { emoji: emoji.custom ? (emoji.name + (emoji.domain || '')) : emoji.native }).then(function () { + const api_emoji = typeof emoji !== 'string' ? (emoji.custom ? (emoji.name + (emoji.domain || '')) : emoji.native) : emoji; + + api(getState).post(`/api/v1/statuses/${status.get('id')}/emoji_reactions`, { emoji: api_emoji }).then(function () { dispatch(emojiReactSuccess(status, emoji)); }).catch(function (error) { dispatch(emojiReactFail(status, emoji, error)); diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index cd9c1fd1a0..81bce49148 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -137,6 +137,7 @@ const excludeTypesFromFilter = filter => { 'follow', 'follow_request', 'favourite', + 'emoji_reaction', 'reblog', 'mention', 'poll', diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index 9251847bad..b6a3124ccb 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -115,6 +115,17 @@ export default class ColumnSettings extends React.PureComponent { </div> </div> + <div role='group' aria-labelledby='notifications-emoji_reaction'> + <span id='notifications-emoji_reaction' className='column-settings__section'><FormattedMessage id='notifications.column_settings.emoji_reaction' defaultMessage='Emoji Reactions:' /></span> + + <div className='column-settings__row'> + <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'emoji_reaction']} onChange={onChange} label={alertStr} /> + {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'emoji_reaction']} onChange={this.onPushChange} label={pushStr} />} + <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'emoji_reaction']} onChange={onChange} label={showStr} /> + <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'emoji_reaction']} onChange={onChange} label={soundStr} /> + </div> + </div> + <div role='group' aria-labelledby='notifications-mention'> <span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> diff --git a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx index 368eb0b7e6..f56ca6a69c 100644 --- a/app/javascript/mastodon/features/notifications/components/filter_bar.jsx +++ b/app/javascript/mastodon/features/notifications/components/filter_bar.jsx @@ -74,6 +74,13 @@ class FilterBar extends React.PureComponent { > <Icon id='star' fixedWidth /> </button> + <button + className={selectedFilter === 'emoji_reaction' ? 'active' : ''} + onClick={this.onClick('emoji_reaction')} + title={intl.formatMessage(tooltips.emojiReactions)} + > + <Icon id='star' fixedWidth /> + </button> <button className={selectedFilter === 'reblog' ? 'active' : ''} onClick={this.onClick('reblog')} diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index 9e2517f084..eab219dfbd 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -15,6 +15,7 @@ import classNames from 'classnames'; const messages = defineMessages({ favourite: { id: 'notification.favourite', defaultMessage: '{name} favourited your status' }, + emojiReaction: { id: 'notification.emoji_reaction', defaultMessage: '{name} reacted your status with emoji' }, follow: { id: 'notification.follow', defaultMessage: '{name} followed you' }, ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' }, poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' }, @@ -213,6 +214,38 @@ class Notification extends ImmutablePureComponent { ); } + renderEmojiReaction (notification, link) { + const { intl, unread } = this.props; + + return ( + <HotKeys handlers={this.getHandlers()}> + <div className={classNames('notification notification-emoji_reaction focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.emojiReaction, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}> + <div className='notification__message'> + <div className='notification__emoji_reaction-icon-wrapper'> + <Icon id='star' className='star-icon' fixedWidth /> + </div> + + <span title={notification.get('created_at')}> + <FormattedMessage id='notification.emoji_reaction' defaultMessage='{name} reacted your status with emoji' values={{ name: link }} /> + </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> + ); + } + renderReblog (notification, link) { const { intl, unread } = this.props; @@ -429,6 +462,8 @@ class Notification extends ImmutablePureComponent { return this.renderMention(notification); case 'favourite': return this.renderFavourite(notification, link); + case 'emoji_reaction': + return this.renderEmojiReaction(notification, link); case 'reblog': return this.renderReblog(notification, link); case 'status': diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index fcb80ae841..28c760f9a0 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -45,11 +45,10 @@ const updateStatusEmojiReaction = (state, emoji_reaction, myId) => { let emoji_reactions = Array.from(status.get('emoji_reactions') || []); if (emoji_reaction.count > 0) { - const old_emoji = emoji_reactions.find((er) => er.name === emoji_reaction.name && er.url === emoji_reaction.url); + const old_emoji = emoji_reactions.find((er) => er.get('name') === emoji_reaction.name && (!er.get('domain') || er.get('domain') === emoji_reaction.domain)); if (old_emoji) { - old_emoji.account_ids = emoji_reaction.account_ids; - old_emoji.count = emoji_reaction.count; - old_emoji.me = emoji_reaction.me; + const index = emoji_reactions.indexOf(old_emoji); + emoji_reactions[index] = old_emoji.merge({ account_ids: emoji_reaction.account_ids, count: emoji_reaction.count, me: emoji_reaction.me }); } else { emoji_reactions.push(ImmutableMap(emoji_reaction)); } diff --git a/app/models/notification.rb b/app/models/notification.rb index 88c5f44b6c..2fadcc65a5 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -25,6 +25,7 @@ class Notification < ApplicationRecord 'Follow' => :follow, 'FollowRequest' => :follow_request, 'Favourite' => :favourite, + 'EmojiReaction' => :emoji_reaction, 'Poll' => :poll, }.freeze @@ -35,6 +36,7 @@ class Notification < ApplicationRecord follow follow_request favourite + emoji_reaction poll update admin.sign_up @@ -46,6 +48,7 @@ class Notification < ApplicationRecord reblog: [status: :reblog], mention: [mention: :status], favourite: [favourite: :status], + emoji_reaction: [emoji_reaction: :status], poll: [poll: :status], update: :status, 'admin.report': [report: :target_account], diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index 137fc53dda..2d9ab53ef5 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -12,7 +12,7 @@ class REST::NotificationSerializer < ActiveModel::Serializer end def status_type? - [:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type) + [:favourite, :emoji_reaction, :reblog, :status, :mention, :poll, :update].include?(object.type) end def report_type? diff --git a/app/services/un_emoji_react_service.rb b/app/services/un_emoji_react_service.rb index 308b3f73d4..ca58c3c1d0 100644 --- a/app/services/un_emoji_react_service.rb +++ b/app/services/un_emoji_react_service.rb @@ -4,14 +4,20 @@ class UnEmojiReactService < BaseService include Redisable include Payloadable - def call(account, status, emoji_reaction = nil) + def call(account_id, status_id, emoji_reaction = nil) + @account_id = account_id + @account = Account.find(account_id) + @status = Status.find(status_id) + if emoji_reaction + p '================================ DEBUG2 G' emoji_reaction.destroy - create_notification(emoji_reaction) if !status.account.local? && status.account.activitypub? - notify_to_followers(emoji_reaction) if status.account.local? + p '================================ DEBUG2 H' + create_notification(emoji_reaction) if !@account.local? && @account.activitypub? + notify_to_followers(emoji_reaction) if @account.local? write_stream(emoji_reaction) else - bulk(account, status) + bulk(@account, @status) end emoji_reaction end @@ -25,33 +31,31 @@ class UnEmojiReactService < BaseService end def create_notification(emoji_reaction) - status = emoji_reaction.status - ActivityPub::DeliveryWorker.perform_async(build_json(emoji_reaction), status.account_id, status.account.inbox_url) + ActivityPub::DeliveryWorker.perform_async(build_json(emoji_reaction), @account_id, @account.inbox_url) end def notify_to_followers(emoji_reaction) - status = emoji_reaction.status - ActivityPub::RawDistributionWorker.perform_async(build_json(emoji_reaction), status.account_id) + ActivityPub::RawDistributionWorker.perform_async(build_json(emoji_reaction), @account_id) end def write_stream(emoji_reaction) - emoji_group = emoji_reaction.status.emoji_reactions_grouped_by_name + emoji_group = @status.emoji_reactions_grouped_by_name .find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.domain) } if emoji_group - emoji_group['status_id'] = emoji_reaction.status_id.to_s + emoji_group['status_id'] = @status.id.to_s else # name: emoji_reaction.name, count: 0, domain: emoji_reaction.domain - emoji_group = { 'name' => emoji_reaction.name, 'count' => 0, 'account_ids' => [], 'status_id' => emoji_reaction.status_id.to_s } + emoji_group = { 'name' => emoji_reaction.name, 'count' => 0, 'account_ids' => [], 'status_id' => @status.id.to_s } emoji_group['domain'] = emoji_reaction.custom_emoji.domain if emoji_reaction.custom_emoji end - FeedAnyJsonWorker.perform_async(render_emoji_reaction(emoji_group), emoji_reaction.status_id, emoji_reaction.account_id) + FeedAnyJsonWorker.perform_async(render_emoji_reaction(emoji_group), @status.id, @account_id) end def build_json(emoji_reaction) Oj.dump(serialize_payload(emoji_reaction, ActivityPub::UndoEmojiReactionSerializer)) end - def render_emoji_reaction(_emoji_reaction, emoji_group) + def render_emoji_reaction(emoji_group) # @rendered_emoji_reaction ||= InlineRenderer.render(emoji_group, nil, :emoji_reaction) Oj.dump(event: :emoji_reaction, payload: emoji_group.to_json) end diff --git a/app/workers/feed_any_json_worker.rb b/app/workers/feed_any_json_worker.rb index 0492c52f2c..33aa254770 100644 --- a/app/workers/feed_any_json_worker.rb +++ b/app/workers/feed_any_json_worker.rb @@ -7,14 +7,29 @@ class FeedAnyJsonWorker include AccountLimitable def perform(payload_json, status_id, my_account_id = nil) + p '========================================= DEBUG AAA' redis.publish("timeline:#{my_account_id}", payload_json) if my_account_id.present? + p '========================================= DEBUG AA' + p status_id + p status_id.to_i status = Status.find(status_id.to_i) + p '========================================= DEBUG AAAAAAAA' + p status.present? if status.present? - scope_status(status).find_each do |account_id| - p account_id if redis.exists?("subscribed:timeline:#{account_id}") - redis.publish("timeline:#{account_id}", payload_json) if redis.exists?("subscribed:timeline:#{account_id}") + p '========================================= DEBUG A' + p scope_status(status) + p '========================================= DEBUG C' + scope_status(status).find_each do |account| + p '========================================= DEBUG D' + p redis.exists?("subscribed:timeline:#{account.id}") + redis.publish("timeline:#{account.id}", payload_json) if redis.exists?("subscribed:timeline:#{account.id}") + end + + if status.visibility.to_sym != :public && status.visibility.to_sym != :unlisted && status.account_id != my_account_id && + redis.exists?("subscribed:timeline:#{status.account_id}") + redis.publish("timeline:#{status.account_id}", payload_json) end end diff --git a/app/workers/un_emoji_react_worker.rb b/app/workers/un_emoji_react_worker.rb index c7c2671099..fce8019fb6 100644 --- a/app/workers/un_emoji_react_worker.rb +++ b/app/workers/un_emoji_react_worker.rb @@ -12,7 +12,7 @@ class UnEmojiReactWorker .find { |reaction| domain == '' ? reaction.custom_emoji.nil? : reaction.custom_emoji&.domain == domain } end - UnEmojiReactService.new.call(Account.find(account_id), Status.find(status_id), emoji_reaction) + UnEmojiReactService.new.call(account_id.to_i, status_id.to_i, emoji_reaction) rescue ActiveRecord::RecordNotFound true end