diff --git a/.cache/.gitkeep b/.cache/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.codeclimate.yml b/.codeclimate.yml index 571507a542..9817d7f1c7 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -27,10 +27,10 @@ plugins: enabled: true eslint: enabled: true - channel: eslint-5 + channel: eslint-6 rubocop: enabled: true - channel: rubocop-0-71 + channel: rubocop-0-76 sass-lint: enabled: true exclude_patterns: diff --git a/.env.nanobox b/.env.nanobox index fc6c3c42f5..03aa01a340 100644 --- a/.env.nanobox +++ b/.env.nanobox @@ -183,6 +183,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io # LDAP_BIND_DN= # LDAP_PASSWORD= # LDAP_UID=cn +# LDAP_MAIL=mail +# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email})) # LDAP_UID_CONVERSION_ENABLED=true # LDAP_UID_CONVERSION_SEARCH=., - # LDAP_UID_CONVERSION_REPLACE=_ diff --git a/.env.production.sample b/.env.production.sample index f5f2343b7e..8121aafeb2 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -203,7 +203,8 @@ STREAMING_CLUSTER_NUM=1 # LDAP_BIND_DN= # LDAP_PASSWORD= # LDAP_UID=cn -# LDAP_SEARCH_FILTER=%{uid}=%{email} +# LDAP_MAIL=mail +# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email})) # LDAP_UID_CONVERSION_ENABLED=true # LDAP_UID_CONVERSION_SEARCH=., - # LDAP_UID_CONVERSION_REPLACE=_ diff --git a/.rubocop.yml b/.rubocop.yml index 8bd4c867f4..9a68becbb6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -71,6 +71,9 @@ Naming/MemoizedInstanceVariableName: Rails: Enabled: true +Rails/EnumHash: + Enabled: false + Rails/HasAndBelongsToMany: Enabled: false @@ -102,6 +105,9 @@ Style/Documentation: Style/DoubleNegation: Enabled: true +Style/FormatStringToken: + Enabled: false + Style/FrozenStringLiteralComment: Enabled: true diff --git a/Gemfile b/Gemfile index 878963fd56..4d6d3aec01 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby '>= 2.4.0', '< 2.7.0' gem 'pkg-config', '~> 1.4' -gem 'puma', '~> 4.2' +gem 'puma', '~> 4.3' gem 'rails', '~> 5.2.3' gem 'thor', '~> 0.20' @@ -15,7 +15,7 @@ gem 'makara', '~> 0.4' gem 'pghero', '~> 2.4' gem 'dotenv-rails', '~> 2.7' -gem 'aws-sdk-s3', '~> 1.55', require: false +gem 'aws-sdk-s3', '~> 1.57', require: false gem 'fog-core', '<= 2.1.0' gem 'fog-openstack', '~> 0.3', require: false gem 'paperclip', '~> 6.0' @@ -91,7 +91,7 @@ gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.2.0' gem 'strong_migrations', '~> 0.4' gem 'tty-command', '~> 0.9', require: false -gem 'tty-prompt', '~> 0.19', require: false +gem 'tty-prompt', '~> 0.20', require: false gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2019' gem 'webpacker', '~> 4.2' @@ -104,7 +104,7 @@ gem 'rdf-normalize', '~> 0.3' gem 'redcarpet', '~> 3.4' group :development, :test do - gem 'fabrication', '~> 2.20' + gem 'fabrication', '~> 2.21' gem 'fuubar', '~> 2.5' gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-byebug', '~> 3.7' @@ -119,7 +119,7 @@ end group :test do gem 'capybara', '~> 3.29' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 2.7' + gem 'faker', '~> 2.8' gem 'microformats', '~> 4.1' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' @@ -138,7 +138,7 @@ group :development do gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' gem 'rubocop', '~> 0.76', require: false - gem 'rubocop-rails', '~> 2.3', require: false + gem 'rubocop-rails', '~> 2.4', require: false gem 'brakeman', '~> 4.7', require: false gem 'bundler-audit', '~> 0.6', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6e1870cf57..091fc78c46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -105,16 +105,16 @@ GEM av (0.9.0) cocaine (~> 0.5.3) aws-eventstream (1.0.3) - aws-partitions (1.240.0) - aws-sdk-core (3.78.0) + aws-partitions (1.246.0) + aws-sdk-core (3.82.0) aws-eventstream (~> 1.0, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.25.0) + aws-sdk-kms (1.26.0) aws-sdk-core (~> 3, >= 3.71.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.55.0) + aws-sdk-s3 (1.57.0) aws-sdk-core (~> 3, >= 3.77.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) @@ -132,7 +132,7 @@ GEM ffi (~> 1.10.0) bootsnap (1.4.5) msgpack (~> 1.0) - brakeman (4.7.1) + brakeman (4.7.2) browser (2.7.1) builder (3.2.3) bullet (6.0.2) @@ -239,8 +239,8 @@ GEM et-orbi (1.1.6) tzinfo excon (0.62.0) - fabrication (2.20.2) - faker (2.7.0) + fabrication (2.21.0) + faker (2.8.0) i18n (>= 1.6, < 1.8) faraday (0.15.4) multipart-post (>= 1.2, < 3) @@ -384,12 +384,12 @@ GEM msgpack (1.3.1) multi_json (1.13.1) multipart-post (2.1.1) - necromancer (0.5.0) + necromancer (0.5.1) net-ldap (0.16.2) net-scp (2.0.0) net-ssh (>= 2.6.5, < 6.0.0) net-ssh (5.2.0) - nio4r (2.5.1) + nio4r (2.5.2) nokogiri (1.10.5) mini_portile2 (~> 2.4.0) nokogumbo (2.0.1) @@ -455,7 +455,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.1) - puma (4.2.0) + puma (4.3.1) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) @@ -568,7 +568,7 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) - rubocop-rails (2.3.2) + rubocop-rails (2.4.0) rack (>= 1.1) rubocop (>= 0.72.0) ruby-progressbar (1.10.1) @@ -637,11 +637,11 @@ GEM tty-command (0.9.0) pastel (~> 0.7.0) tty-cursor (0.7.0) - tty-prompt (0.19.0) + tty-prompt (0.20.0) necromancer (~> 0.5.0) pastel (~> 0.7.0) - tty-reader (~> 0.6.0) - tty-reader (0.6.0) + tty-reader (~> 0.7.0) + tty-reader (0.7.0) tty-cursor (~> 0.7) tty-screen (~> 0.7) wisper (~> 2.0.0) @@ -673,7 +673,7 @@ GEM websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) - wisper (2.0.0) + wisper (2.0.1) xpath (3.2.0) nokogiri (~> 1.8) @@ -685,7 +685,7 @@ DEPENDENCIES active_record_query_trace (~> 1.7) addressable (~> 2.7) annotate (~> 3.0) - aws-sdk-s3 (~> 1.55) + aws-sdk-s3 (~> 1.57) better_errors (~> 2.5) binding_of_caller (~> 0.7) blurhash (~> 0.1) @@ -712,8 +712,8 @@ DEPENDENCIES discard (~> 1.1) doorkeeper (~> 5.2) dotenv-rails (~> 2.7) - fabrication (~> 2.20) - faker (~> 2.7) + fabrication (~> 2.21) + faker (~> 2.8) fast_blank (~> 1.0) fastimage fog-core (<= 2.1.0) @@ -767,7 +767,7 @@ DEPENDENCIES private_address_check (~> 0.5) pry-byebug (~> 3.7) pry-rails (~> 0.3) - puma (~> 4.2) + puma (~> 4.3) pundit (~> 2.1) rack-attack (~> 6.2) rack-cors (~> 1.1) @@ -784,7 +784,7 @@ DEPENDENCIES rspec-rails (~> 3.9) rspec-sidekiq (~> 3.0) rubocop (~> 0.76) - rubocop-rails (~> 2.3) + rubocop-rails (~> 2.4) ruby-progressbar (~> 1.10) sanitize (~> 5.1) sidekiq (~> 5.2) @@ -801,7 +801,7 @@ DEPENDENCIES strong_migrations (~> 0.4) thor (~> 0.20) tty-command (~> 0.9) - tty-prompt (~> 0.19) + tty-prompt (~> 0.20) twitter-text (~> 1.14) tzinfo-data (~> 1.2019) webmock (~> 3.7) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 33df75b374..144fdd6ac9 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -20,6 +20,10 @@ class Api::BaseController < ApplicationController render json: { error: e.to_s }, status: 422 end + rescue_from ActiveRecord::RecordNotUnique do + render json: { error: 'Duplicate record' }, status: 422 + end + rescue_from ActiveRecord::RecordNotFound do render json: { error: 'Record not found' }, status: 404 end diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 1b658f8708..1cbc92b93d 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -51,6 +51,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController def data_params return {} if params[:data].blank? - params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) + params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index d8153e082f..f388b17e5d 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -19,6 +19,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController data = { alerts: { follow: alerts_enabled, + follow_request: false, favourite: alerts_enabled, reblog: alerts_enabled, mention: alerts_enabled, @@ -58,6 +59,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController end def data_params - @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll]) + @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index e07276f0c6..7fcc4e8162 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -62,6 +62,8 @@ module AccountsHelper def account_badge(account, all: false) if account.bot? content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles') + elsif account.group? + content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles') elsif (Setting.show_staff_badge && account.user_staff?) || all content_tag(:div, class: 'roles') do if all && !account.user_staff? diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 7effb07d1c..940f3c3d44 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -121,7 +121,7 @@ const excludeTypesFromSettings = state => state.getIn(['settings', 'notification const excludeTypesFromFilter = filter => { - const allTypes = ImmutableList(['follow', 'favourite', 'reblog', 'mention', 'poll']); + const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll']); return allTypes.filterNot(item => item === filter).toJS(); }; diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js index 2d2a7cbe01..62965df949 100644 --- a/app/javascript/flavours/glitch/components/poll.js +++ b/app/javascript/flavours/glitch/components/poll.js @@ -67,9 +67,7 @@ class Poll extends ImmutablePureComponent { } } - handleOptionChange = e => { - const { target: { value } } = e; - + _toggleOption = value => { if (this.props.poll.get('multiple')) { const tmp = { ...this.state.selected }; if (tmp[value]) { @@ -83,8 +81,20 @@ class Poll extends ImmutablePureComponent { tmp[value] = true; this.setState({ selected: tmp }); } + } + + handleOptionChange = ({ target: { value } }) => { + this._toggleOption(value); }; + handleOptionKeyPress = (e) => { + if (e.key === 'Enter' || e.key === ' ') { + this._toggleOption(e.target.getAttribute('data-index')); + e.stopPropagation(); + e.preventDefault(); + } + } + handleVote = () => { if (this.props.disabled) { return; @@ -135,7 +145,17 @@ class Poll extends ImmutablePureComponent { disabled={disabled} /> - {!showResults && } + {!showResults && ( + + )} {showResults && {!!voted && } {Math.round(percent)}% diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index e65a68b4d2..6b4aff6161 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -232,9 +232,18 @@ class Header extends ImmutablePureComponent { const content = { __html: account.get('note_emojified') }; const displayNameHtml = { __html: account.get('display_name_html') }; const fields = account.get('fields'); - const badge = account.get('bot') ? (
) : null; const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + let badge; + + if (account.get('bot')) { + badge = (
); + } else if (account.get('group')) { + badge = (
); + } else { + badge = null; + } + return (
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js index e29bd61f53..e4d5d0eda3 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js @@ -58,6 +58,17 @@ export default class ColumnSettings extends React.PureComponent {
+
+ + +
+ + {showPushSettings && } + + +
+
+
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js new file mode 100644 index 0000000000..d73dac4348 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js @@ -0,0 +1,130 @@ +import React, { Fragment } from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import Avatar from 'flavours/glitch/components/avatar'; +import DisplayName from 'flavours/glitch/components/display_name'; +import Permalink from 'flavours/glitch/components/permalink'; +import IconButton from 'flavours/glitch/components/icon_button'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import NotificationOverlayContainer from '../containers/overlay_container'; +import { HotKeys } from 'react-hotkeys'; +import Icon from 'flavours/glitch/components/icon'; + +const messages = defineMessages({ + authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, + reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }, +}); + +export default @injectIntl +class FollowRequest extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + onAuthorize: PropTypes.func.isRequired, + onReject: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + notification: ImmutablePropTypes.map.isRequired, + }; + + handleMoveUp = () => { + const { notification, onMoveUp } = this.props; + onMoveUp(notification.get('id')); + } + + handleMoveDown = () => { + const { notification, onMoveDown } = this.props; + onMoveDown(notification.get('id')); + } + + handleOpen = () => { + this.handleOpenProfile(); + } + + handleOpenProfile = () => { + const { notification } = this.props; + this.context.router.history.push(`/accounts/${notification.getIn(['account', 'id'])}`); + } + + handleMention = e => { + e.preventDefault(); + + const { notification, onMention } = this.props; + onMention(notification.get('account'), this.context.router.history); + } + + getHandlers () { + return { + moveUp: this.handleMoveUp, + moveDown: this.handleMoveDown, + open: this.handleOpen, + openProfile: this.handleOpenProfile, + mention: this.handleMention, + reply: this.handleMention, + }; + } + + render () { + const { intl, hidden, account, onAuthorize, onReject, notification } = this.props; + + if (!account) { + return
; + } + + if (hidden) { + return ( + + {account.get('display_name')} + {account.get('username')} + + ); + } + + // Links to the display name. + const displayName = account.get('display_name_html') || account.get('username'); + const link = ( + + ); + + return ( + +
+
+
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+
+
+ + +
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js index 5c5bbf604f..62fc28386b 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.js +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js @@ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; // Our imports, import StatusContainer from 'flavours/glitch/containers/status_container'; import NotificationFollow from './follow'; +import NotificationFollowRequestContainer from '../containers/follow_request_container'; export default class Notification extends ImmutablePureComponent { @@ -47,6 +48,18 @@ export default class Notification extends ImmutablePureComponent { onMention={onMention} /> ); + case 'follow_request': + return ( +