about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock3
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.js25
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js32
-rw-r--r--db/migrate/20171226094803_more_faster_index_on_notifications.rb7
-rw-r--r--db/schema.rb4
6 files changed, 67 insertions, 5 deletions
diff --git a/Gemfile b/Gemfile
index 5e91d1425..bea840041 100644
--- a/Gemfile
+++ b/Gemfile
@@ -59,6 +59,7 @@ gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'rqrcode', '~> 0.10'
 gem 'ruby-oembed', '~> 0.12', require: 'oembed'
+gem 'ruby-progressbar', '~> 1.4'
 gem 'sanitize', '~> 4.4'
 gem 'sidekiq', '~> 5.0'
 gem 'sidekiq-scheduler', '~> 2.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 72d323dcb..8af7872af 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -623,6 +623,7 @@ DEPENDENCIES
   rspec-sidekiq (~> 3.0)
   rubocop
   ruby-oembed (~> 0.12)
+  ruby-progressbar (~> 1.4)
   sanitize (~> 4.4)
   scss_lint (~> 0.55)
   sidekiq (~> 5.0)
@@ -645,4 +646,4 @@ RUBY VERSION
    ruby 2.4.2p198
 
 BUNDLED WITH
-   1.16.0
+   1.16.1
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js
index 3190fd0be..573c3743f 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js
@@ -13,6 +13,10 @@ const messages = defineMessages({
   reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
   favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
+  mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
+  muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
+  unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
+  block: { id: 'status.block', defaultMessage: 'Block @{name}' },
   report: { id: 'status.report', defaultMessage: 'Report @{name}' },
   share: { id: 'status.share', defaultMessage: 'Share' },
   pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
@@ -32,6 +36,9 @@ export default class ActionBar extends React.PureComponent {
     onReply: PropTypes.func.isRequired,
     onReblog: PropTypes.func.isRequired,
     onFavourite: PropTypes.func.isRequired,
+    onMute: PropTypes.func,
+    onMuteConversation: PropTypes.func,
+    onBlock: PropTypes.func,
     onDelete: PropTypes.func.isRequired,
     onMention: PropTypes.func.isRequired,
     onReport: PropTypes.func,
@@ -60,6 +67,18 @@ export default class ActionBar extends React.PureComponent {
     this.props.onMention(this.props.status.get('account'), this.context.router.history);
   }
 
+  handleMuteClick = () => {
+    this.props.onMute(this.props.status.get('account'));
+  }
+
+  handleConversationMuteClick = () => {
+    this.props.onMuteConversation(this.props.status);
+  }
+
+  handleBlockClick = () => {
+    this.props.onBlock(this.props.status.get('account'));
+  }
+
   handleReport = () => {
     this.props.onReport(this.props.status);
   }
@@ -83,6 +102,7 @@ export default class ActionBar extends React.PureComponent {
     const { status, intl } = this.props;
 
     const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
+    const mutingConversation = status.get('muted');
 
     let menu = [];
 
@@ -95,10 +115,15 @@ export default class ActionBar extends React.PureComponent {
         menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
       }
 
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
+      menu.push(null);
       menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
     } else {
       menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
       menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
+      menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
       menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
     }
 
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 40ae380ab..682c3625f 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -20,14 +20,16 @@ import {
   replyCompose,
   mentionCompose,
 } from 'flavours/glitch/actions/compose';
-import { deleteStatus } from 'flavours/glitch/actions/statuses';
+import { blockAccount } from 'flavours/glitch/actions/accounts';
+import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses';
+import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initReport } from 'flavours/glitch/actions/reports';
 import { makeGetStatus } from 'flavours/glitch/selectors';
 import { ScrollContainer } from 'react-router-scroll-4';
 import ColumnBackButton from 'flavours/glitch/components/column_back_button';
 import StatusContainer from 'flavours/glitch/containers/status_container';
 import { openModal } from 'flavours/glitch/actions/modal';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { HotKeys } from 'react-hotkeys';
 import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state';
@@ -36,6 +38,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
 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' },
 });
 
 const makeMapStateToProps = () => {
@@ -165,6 +168,28 @@ export default class Status extends ImmutablePureComponent {
     this.props.dispatch(openModal('VIDEO', { media, time }));
   }
 
+  handleMuteClick = (account) => {
+    this.props.dispatch(initMuteModal(account));
+  }
+
+  handleConversationMuteClick = (status) => {
+    if (status.get('muted')) {
+      this.props.dispatch(unmuteStatus(status.get('id')));
+    } else {
+      this.props.dispatch(muteStatus(status.get('id')));
+    }
+  }
+
+  handleBlockClick = (account) => {
+    const { dispatch, intl } = this.props;
+
+    dispatch(openModal('CONFIRM', {
+      message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
+      confirm: intl.formatMessage(messages.blockConfirm),
+      onConfirm: () => dispatch(blockAccount(account.get('id'))),
+    }));
+  }
+
   handleReport = (status) => {
     this.props.dispatch(initReport(status.get('account'), status));
   }
@@ -349,6 +374,9 @@ export default class Status extends ImmutablePureComponent {
                   onReblog={this.handleReblogClick}
                   onDelete={this.handleDeleteClick}
                   onMention={this.handleMentionClick}
+                  onMute={this.handleMuteClick}
+                  onMuteConversation={this.handleConversationMuteClick}
+                  onBlock={this.handleBlockClick}
                   onReport={this.handleReport}
                   onPin={this.handlePin}
                   onEmbed={this.handleEmbed}
diff --git a/db/migrate/20171226094803_more_faster_index_on_notifications.rb b/db/migrate/20171226094803_more_faster_index_on_notifications.rb
new file mode 100644
index 000000000..b2e53b82d
--- /dev/null
+++ b/db/migrate/20171226094803_more_faster_index_on_notifications.rb
@@ -0,0 +1,7 @@
+class MoreFasterIndexOnNotifications < ActiveRecord::Migration[5.1]
+  def change
+    commit_db_transaction
+    add_index :notifications, [:account_id, :id], order: { id: :desc }, algorithm: :concurrently
+    remove_index :notifications, name: :index_notifications_on_id_and_account_id_and_activity_type
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9410cdab5..b480c9c46 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20171212195226) do
+ActiveRecord::Schema.define(version: 20171226094803) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -268,8 +268,8 @@ ActiveRecord::Schema.define(version: 20171212195226) do
     t.bigint "account_id"
     t.bigint "from_account_id"
     t.index ["account_id", "activity_id", "activity_type"], name: "account_activity", unique: true
+    t.index ["account_id", "id"], name: "index_notifications_on_account_id_and_id", order: { id: :desc }
     t.index ["activity_id", "activity_type"], name: "index_notifications_on_activity_id_and_activity_type"
-    t.index ["id", "account_id", "activity_type"], name: "index_notifications_on_id_and_account_id_and_activity_type", order: { id: :desc }
   end
 
   create_table "oauth_access_grants", force: :cascade do |t|