From f60cd97638c9de9cbc4714cfbc87a321324fe9ff Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 1 Dec 2019 17:24:33 +0100 Subject: Only normalize local polls (#12515) Before this patch, if remote poll options have leading or trailing spaces, the information stored locally won't match them, causing federated voting to fail. --- app/models/poll.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/models') diff --git a/app/models/poll.rb b/app/models/poll.rb index 5427368fd..b5deafcc2 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -36,7 +36,7 @@ class Poll < ApplicationRecord scope :attached, -> { where.not(status_id: nil) } scope :unattached, -> { where(status_id: nil) } - before_validation :prepare_options + before_validation :prepare_options, if: :local? before_validation :prepare_votes_count after_initialize :prepare_cached_tallies -- cgit From 911cc144815babf83ddf99f2daa3682021d401b8 Mon Sep 17 00:00:00 2001 From: ThibG Date: Sun, 1 Dec 2019 17:25:29 +0100 Subject: Add follow_request notification type (#12198) * Add follow_request notification type The notification type already existed in the backend but was never pushed to the front-end. This also means translation strings were also available for the backend, from the notification mailer. Unlike other notification types, these are off by default, to match what I remember of Gargron's view on the topic: that follow requests should not clutter notifications and should instead be reviewed at the user's own leisure in the dedicated column. Since follow requests have their own column, I've deemed it unnecessary to add a specific tab for them in the notification quick filter. * Show follow request link in single-column if there are pending requests, even if account isn't locked * Push follow requests from notifications to the follow_requests list * Offer to accept or reject follow request from the notification * Redesign follow request notification --- .../api/v1/push/subscriptions_controller.rb | 2 +- .../api/web/push_subscriptions_controller.rb | 3 +- app/javascript/mastodon/actions/notifications.js | 2 +- .../notifications/components/column_settings.js | 11 ++++ .../notifications/components/follow_request.js | 59 ++++++++++++++++++++++ .../notifications/components/notification.js | 27 +++++++++- .../containers/follow_request_container.js | 26 ++++++++++ .../ui/components/follow_requests_nav_link.js | 13 ++--- app/javascript/mastodon/reducers/notifications.js | 11 +++- .../mastodon/reducers/push_notifications.js | 1 + app/javascript/mastodon/reducers/settings.js | 3 ++ app/javascript/mastodon/reducers/user_lists.js | 11 ++++ .../mastodon/service_worker/web_push_locales.js | 1 + app/models/notification.rb | 8 +-- app/services/notify_service.rb | 2 +- spec/models/notification_spec.rb | 26 ---------- 16 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 app/javascript/mastodon/features/notifications/components/follow_request.js create mode 100644 app/javascript/mastodon/features/notifications/containers/follow_request_container.js (limited to 'app/models') diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 1b658f870..1cbc92b93 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 d8153e082..f388b17e5 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/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 3a92e0224..798f9b37e 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -110,7 +110,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS(); 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/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js index 60a86312a..8bd03fbda 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.js +++ b/app/javascript/mastodon/features/notifications/components/column_settings.js @@ -57,6 +57,17 @@ export default class ColumnSettings extends React.PureComponent { +
+ + +
+ + {showPushSettings && } + + +
+
+
diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.js b/app/javascript/mastodon/features/notifications/components/follow_request.js new file mode 100644 index 000000000..a80cfb2fa --- /dev/null +++ b/app/javascript/mastodon/features/notifications/components/follow_request.js @@ -0,0 +1,59 @@ +import React, { Fragment } from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import Avatar from 'mastodon/components/avatar'; +import DisplayName from 'mastodon/components/display_name'; +import Permalink from 'mastodon/components/permalink'; +import IconButton from 'mastodon/components/icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +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, + }; + + render () { + const { intl, hidden, account, onAuthorize, onReject } = this.props; + + if (!account) { + return
; + } + + if (hidden) { + return ( + + {account.get('display_name')} + {account.get('username')} + + ); + } + + return ( +
+
+ +
+ +
+ +
+ + +
+
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 2dea8afa7..74065e5e2 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -7,6 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { me } from 'mastodon/initial_state'; import StatusContainer from 'mastodon/containers/status_container'; import AccountContainer from 'mastodon/containers/account_container'; +import FollowRequestContainer from '../containers/follow_request_container'; import Icon from 'mastodon/components/icon'; import Permalink from 'mastodon/components/permalink'; @@ -127,7 +128,29 @@ class Notification extends ImmutablePureComponent {
-
+ + ); + } + + renderFollowRequest (notification, account, link) { + const { intl } = this.props; + + return ( + +
+
+
+ +
+ + + + +
+ +
); @@ -261,6 +284,8 @@ class Notification extends ImmutablePureComponent { switch(notification.get('type')) { case 'follow': return this.renderFollow(notification, account, link); + case 'follow_request': + return this.renderFollowRequest(notification, account, link); case 'mention': return this.renderMention(notification); case 'favourite': diff --git a/app/javascript/mastodon/features/notifications/containers/follow_request_container.js b/app/javascript/mastodon/features/notifications/containers/follow_request_container.js new file mode 100644 index 000000000..f9f6c577e --- /dev/null +++ b/app/javascript/mastodon/features/notifications/containers/follow_request_container.js @@ -0,0 +1,26 @@ +import { connect } from 'react-redux'; +import { makeGetAccount } from 'mastodon/selectors'; +import FollowRequest from '../components/follow_request'; +import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts'; + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = (state, props) => ({ + account: getAccount(state, props.id), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = (dispatch, { id }) => ({ + onAuthorize () { + dispatch(authorizeFollowRequest(id)); + }, + + onReject () { + dispatch(rejectFollowRequest(id)); + }, +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(FollowRequest); diff --git a/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js b/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js index 90c953893..950ed7b27 100644 --- a/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js +++ b/app/javascript/mastodon/features/ui/components/follow_requests_nav_link.js @@ -4,12 +4,10 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts'; import { connect } from 'react-redux'; import { NavLink, withRouter } from 'react-router-dom'; import IconWithBadge from 'mastodon/components/icon_with_badge'; -import { me } from 'mastodon/initial_state'; import { List as ImmutableList } from 'immutable'; import { FormattedMessage } from 'react-intl'; const mapStateToProps = state => ({ - locked: state.getIn(['accounts', me, 'locked']), count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, }); @@ -19,22 +17,19 @@ class FollowRequestsNavLink extends React.Component { static propTypes = { dispatch: PropTypes.func.isRequired, - locked: PropTypes.bool, count: PropTypes.number.isRequired, }; componentDidMount () { - const { dispatch, locked } = this.props; + const { dispatch } = this.props; - if (locked) { - dispatch(fetchFollowRequests()); - } + dispatch(fetchFollowRequests()); } render () { - const { locked, count } = this.props; + const { count } = this.props; - if (!locked || count === 0) { + if (count === 0) { return null; } diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 6ba80bd6a..60e901e39 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -13,6 +13,8 @@ import { import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, + FOLLOW_REQUEST_AUTHORIZE_SUCCESS, + FOLLOW_REQUEST_REJECT_SUCCESS, } from '../actions/accounts'; import { DOMAIN_BLOCK_SUCCESS } from 'mastodon/actions/domain_blocks'; import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines'; @@ -89,8 +91,8 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece }); }; -const filterNotifications = (state, accountIds) => { - const helper = list => list.filterNot(item => item !== null && accountIds.includes(item.get('account'))); +const filterNotifications = (state, accountIds, type) => { + const helper = list => list.filterNot(item => item !== null && accountIds.includes(item.get('account')) && (type === undefined || type === item.get('type'))); return state.update('items', helper).update('pendingItems', helper); }; @@ -129,6 +131,11 @@ export default function notifications(state = initialState, action) { return action.relationship.muting_notifications ? filterNotifications(state, [action.relationship.id]) : state; case DOMAIN_BLOCK_SUCCESS: return filterNotifications(state, action.accounts); + case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: + case FOLLOW_REQUEST_REJECT_SUCCESS: + return filterNotifications(state, [action.id], 'follow_request'); + case ACCOUNT_MUTE_SUCCESS: + return action.relationship.muting_notifications ? filterNotifications(state, [action.relationship.id]) : state; case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: diff --git a/app/javascript/mastodon/reducers/push_notifications.js b/app/javascript/mastodon/reducers/push_notifications.js index 317352b79..c48cfb705 100644 --- a/app/javascript/mastodon/reducers/push_notifications.js +++ b/app/javascript/mastodon/reducers/push_notifications.js @@ -6,6 +6,7 @@ const initialState = Immutable.Map({ subscription: null, alerts: new Immutable.Map({ follow: false, + follow_request: false, favourite: false, reblog: false, mention: false, diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 793a99f8f..efef2ad9a 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -30,6 +30,7 @@ const initialState = ImmutableMap({ notifications: ImmutableMap({ alerts: ImmutableMap({ follow: true, + follow_request: false, favourite: true, reblog: true, mention: true, @@ -44,6 +45,7 @@ const initialState = ImmutableMap({ shows: ImmutableMap({ follow: true, + follow_request: false, favourite: true, reblog: true, mention: true, @@ -52,6 +54,7 @@ const initialState = ImmutableMap({ sounds: ImmutableMap({ follow: true, + follow_request: false, favourite: true, reblog: true, mention: true, diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 08e94022f..a7853452f 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -1,3 +1,6 @@ +import { + NOTIFICATIONS_UPDATE, +} from '../actions/notifications'; import { FOLLOWERS_FETCH_SUCCESS, FOLLOWERS_EXPAND_SUCCESS, @@ -53,6 +56,12 @@ const appendToList = (state, type, id, accounts, next) => { }); }; +const normalizeFollowRequest = (state, notification) => { + return state.updateIn(['follow_requests', 'items'], list => { + return list.filterNot(item => item === notification.account.id).unshift(notification.account.id); + }); +}; + export default function userLists(state = initialState, action) { switch(action.type) { case FOLLOWERS_FETCH_SUCCESS: @@ -67,6 +76,8 @@ export default function userLists(state = initialState, action) { return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id))); case FAVOURITES_FETCH_SUCCESS: return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id))); + case NOTIFICATIONS_UPDATE: + return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state; case FOLLOW_REQUESTS_FETCH_SUCCESS: return state.setIn(['follow_requests', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next); case FOLLOW_REQUESTS_EXPAND_SUCCESS: diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js index 5ce8c7b50..1265f3cfa 100644 --- a/app/javascript/mastodon/service_worker/web_push_locales.js +++ b/app/javascript/mastodon/service_worker/web_push_locales.js @@ -16,6 +16,7 @@ filenames.forEach(filename => { filtered[locale] = { 'notification.favourite': full['notification.favourite'] || '', 'notification.follow': full['notification.follow'] || '', + 'notification.follow_request': full['notification.follow_request'] || '', 'notification.mention': full['notification.mention'] || '', 'notification.reblog': full['notification.reblog'] || '', 'notification.poll': full['notification.poll'] || '', diff --git a/app/models/notification.rb b/app/models/notification.rb index 498673ff1..ad7528f50 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -42,7 +42,7 @@ class Notification < ApplicationRecord validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values } scope :browserable, ->(exclude_types = [], account_id = nil) { - types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types + [:follow_request]) + types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types) if account_id.nil? where(activity_type: types) else @@ -50,7 +50,7 @@ class Notification < ApplicationRecord end } - cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, poll: [status: STATUS_INCLUDES] + cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, follow_request: :account, poll: [status: STATUS_INCLUDES] def type @type ||= TYPE_CLASS_MAP.invert[activity_type].to_sym @@ -69,10 +69,6 @@ class Notification < ApplicationRecord end end - def browserable? - type != :follow_request - end - class << self def cache_ids select(:id, :updated_at, :activity_type, :activity_id) diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index b5c721589..9364a6ae8 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -9,7 +9,7 @@ class NotifyService < BaseService return if recipient.user.nil? || blocked? create_notification! - push_notification! if @notification.browserable? + push_notification! push_to_conversation! if direct_message? send_email! if email_enabled? rescue ActiveRecord::RecordInvalid diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 59c582cde..d2e676ec2 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -34,32 +34,6 @@ RSpec.describe Notification, type: :model do end end - describe '#browserable?' do - let(:notification) { Fabricate(:notification) } - - subject { notification.browserable? } - - context 'type is :follow_request' do - before do - allow(notification).to receive(:type).and_return(:follow_request) - end - - it 'returns false' do - is_expected.to be false - end - end - - context 'type is not :follow_request' do - before do - allow(notification).to receive(:type).and_return(:else) - end - - it 'returns true' do - is_expected.to be true - end - end - end - describe '#type' do it 'returns :reblog for a Status' do notification = Notification.new(activity: Status.new) -- cgit From bd8dc9bd0c9857470f14189c15572cab18bf8ab1 Mon Sep 17 00:00:00 2001 From: Mathieu Brunot Date: Sun, 1 Dec 2019 18:52:21 +0100 Subject: :sparkles: Add an LDAP Mail attribute config (#12053) Signed-off-by: mathieu.brunot --- .env.nanobox | 2 ++ .env.production.sample | 3 ++- app/models/concerns/ldap_authenticable.rb | 4 ++-- config/initializers/devise.rb | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) (limited to 'app/models') diff --git a/.env.nanobox b/.env.nanobox index fc6c3c42f..03aa01a34 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 6b078c7b2..9cab992e3 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -178,7 +178,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/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb index 2d2e1edbb..e3f94bb6c 100644 --- a/app/models/concerns/ldap_authenticable.rb +++ b/app/models/concerns/ldap_authenticable.rb @@ -6,7 +6,7 @@ module LdapAuthenticable class_methods do def authenticate_with_ldap(params = {}) ldap = Net::LDAP.new(ldap_options) - filter = format(Devise.ldap_search_filter, uid: Devise.ldap_uid, email: params[:email]) + filter = format(Devise.ldap_search_filter, uid: Devise.ldap_uid, mail: Devise.ldap_mail, email: params[:email]) if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: filter, password: params[:password])) ldap_get_user(user_info.first) @@ -25,7 +25,7 @@ module LdapAuthenticable resource = joins(:account).find_by(accounts: { username: safe_username }) if resource.blank? - resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc) + resource = new(email: attributes[Devise.ldap_mail.to_sym].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc) resource.save! end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index fa9fd8cc4..59e69ad37 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -53,6 +53,8 @@ module Devise @@ldap_base = nil mattr_accessor :ldap_uid @@ldap_uid = nil + mattr_accessor :ldap_mail + @@ldap_mail = nil mattr_accessor :ldap_bind_dn @@ldap_bind_dn = nil mattr_accessor :ldap_password @@ -369,8 +371,9 @@ Devise.setup do |config| config.ldap_bind_dn = ENV.fetch('LDAP_BIND_DN') config.ldap_password = ENV.fetch('LDAP_PASSWORD') config.ldap_uid = ENV.fetch('LDAP_UID', 'cn') + config.ldap_mail = ENV.fetch('LDAP_MAIL', 'mail') config.ldap_tls_no_verify = ENV['LDAP_TLS_NO_VERIFY'] == 'true' - config.ldap_search_filter = ENV.fetch('LDAP_SEARCH_FILTER', '%{uid}=%{email}') + config.ldap_search_filter = ENV.fetch('LDAP_SEARCH_FILTER', '(|(%{uid}=%{email})(%{mail}=%{email}))') config.ldap_uid_conversion_enabled = ENV['LDAP_UID_CONVERSION_ENABLED'] == 'true' config.ldap_uid_conversion_search = ENV.fetch('LDAP_UID_CONVERSION_SEARCH', '.,- ') config.ldap_uid_conversion_replace = ENV.fetch('LDAP_UID_CONVERSION_REPLACE', '_') -- cgit From 6be16d02cb3f03d21226f9b6664d33e3e8650043 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 3 Dec 2019 02:25:43 +0900 Subject: Update ESLint and RuboCop in Code Climate (#12534) --- .codeclimate.yml | 4 ++-- .rubocop.yml | 6 ++++++ app/lib/activitypub/adapter.rb | 1 + app/lib/language_detector.rb | 2 +- app/models/media_attachment.rb | 2 +- lib/paperclip/lazy_thumbnail.rb | 4 ++-- 6 files changed, 13 insertions(+), 6 deletions(-) (limited to 'app/models') diff --git a/.codeclimate.yml b/.codeclimate.yml index 571507a54..9817d7f1c 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/.rubocop.yml b/.rubocop.yml index 8bd4c867f..9a68becbb 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/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb index 2a8f72333..78138fb73 100644 --- a/app/lib/activitypub/adapter.rb +++ b/app/lib/activitypub/adapter.rb @@ -35,6 +35,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base def serializable_hash(options = nil) named_contexts = {} context_extensions = {} + options = serialization_options(options) serialized_hash = serializer.serializable_hash(options.merge(named_contexts: named_contexts, context_extensions: context_extensions)) serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields] diff --git a/app/lib/language_detector.rb b/app/lib/language_detector.rb index 6f9511a54..302072bcc 100644 --- a/app/lib/language_detector.rb +++ b/app/lib/language_detector.rb @@ -44,7 +44,7 @@ class LanguageDetector words = text.scan(RELIABLE_CHARACTERS_RE) if words.present? - words.reduce(0) { |acc, elem| acc + elem.size }.to_f / text.size.to_f > 0.3 + words.reduce(0) { |acc, elem| acc + elem.size }.to_f / text.size > 0.3 else false end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 1f9d92b22..5d5034a52 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -287,7 +287,7 @@ class MediaAttachment < ApplicationRecord width: width, height: height, size: "#{width}x#{height}", - aspect: width.to_f / height.to_f, + aspect: width.to_f / height, } end diff --git a/lib/paperclip/lazy_thumbnail.rb b/lib/paperclip/lazy_thumbnail.rb index 542c17fb2..10b14860c 100644 --- a/lib/paperclip/lazy_thumbnail.rb +++ b/lib/paperclip/lazy_thumbnail.rb @@ -9,8 +9,8 @@ module Paperclip min_side = [@current_geometry.width, @current_geometry.height].min.to_i options[:geometry] = "#{min_side}x#{min_side}#" if @target_geometry.square? && min_side < @target_geometry.width elsif options[:pixels] - width = Math.sqrt(options[:pixels] * (@current_geometry.width.to_f / @current_geometry.height.to_f)).round.to_i - height = Math.sqrt(options[:pixels] * (@current_geometry.height.to_f / @current_geometry.width.to_f)).round.to_i + width = Math.sqrt(options[:pixels] * (@current_geometry.width.to_f / @current_geometry.height)).round.to_i + height = Math.sqrt(options[:pixels] * (@current_geometry.height.to_f / @current_geometry.width)).round.to_i options[:geometry] = "#{width}x#{height}>" end -- cgit From f43f1e01840cd0bad7a88c90d9ea44b183a2a15d Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Thu, 5 Dec 2019 04:36:33 +0900 Subject: Add basic support for group actors (#12071) * Show badge on group actor in WebUI * Do not notify in case of by following group actor * If you mention group actor, also mention group actor followers * Relax characters that can be used in username (same as Application) * Revert "Relax characters that can be used in username (same as Application)" This reverts commit 7e10a137b878d0db1b5252c52106faef5e09ca4b. * Delete display_name method --- app/helpers/statuses_helper.rb | 68 ++++++++++++++++++++++ .../mastodon/features/account/components/header.js | 11 +++- app/lib/activitypub/activity.rb | 6 +- app/lib/activitypub/tag_manager.rb | 30 ++++++++-- app/models/account.rb | 7 +++ app/serializers/activitypub/actor_serializer.rb | 2 + app/serializers/rest/account_serializer.rb | 2 +- config/locales/en.yml | 1 + 8 files changed, 118 insertions(+), 9 deletions(-) (limited to 'app/models') diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb index 866a9902c..f0e3df944 100644 --- a/app/helpers/statuses_helper.rb +++ b/app/helpers/statuses_helper.rb @@ -4,6 +4,74 @@ module StatusesHelper EMBEDDED_CONTROLLER = 'statuses' EMBEDDED_ACTION = 'embed' + def account_action_button(account) + if user_signed_in? + if account.id == current_user.account_id + link_to settings_profile_url, class: 'button logo-button' do + safe_join([svg_logo, t('settings.edit_profile')]) + end + elsif current_account.following?(account) || current_account.requested?(account) + link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do + safe_join([svg_logo, t('accounts.unfollow')]) + end + elsif !(account.memorial? || account.moved?) + link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do + safe_join([svg_logo, t('accounts.follow')]) + end + end + elsif !(account.memorial? || account.moved?) + link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do + safe_join([svg_logo, t('accounts.follow')]) + end + end + end + + def minimal_account_action_button(account) + if user_signed_in? + return if account.id == current_user.account_id + + if current_account.following?(account) || current_account.requested?(account) + link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do + fa_icon('user-times fw') + end + elsif !(account.memorial? || account.moved?) + link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do + fa_icon('user-plus fw') + end + end + elsif !(account.memorial? || account.moved?) + link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do + fa_icon('user-plus fw') + end + end + end + + def svg_logo + content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976') + end + + def svg_logo_full + content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678') + end + + 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? + content_tag(:div, t('admin.accounts.roles.user'), class: 'account-role') + elsif account.user_admin? + content_tag(:div, t('accounts.roles.admin'), class: 'account-role admin') + elsif account.user_moderator? + content_tag(:div, t('accounts.roles.moderator'), class: 'account-role moderator') + end + end + end + end + def link_to_more(url) link_to t('statuses.show_more'), url, class: 'load-more load-gap' end diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index dbb567e85..8bd7f2db5 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -238,9 +238,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/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index cdd406043..0ca6b92a4 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -89,7 +89,7 @@ class ActivityPub::Activity def distribute(status) crawl_links(status) - notify_about_reblog(status) if reblog_of_local_account?(status) + notify_about_reblog(status) if reblog_of_local_account?(status) && !reblog_by_following_group_account?(status) notify_about_mentions(status) # Only continue if the status is supposed to have arrived in real-time. @@ -105,6 +105,10 @@ class ActivityPub::Activity status.reblog? && status.reblog.account.local? end + def reblog_by_following_group_account?(status) + status.reblog? && status.account.group? && status.reblog.account.following?(status.account) + end + def notify_about_reblog(status) NotifyService.new.call(status.reblog.account, status) end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 512272dbe..ed680d762 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -68,10 +68,19 @@ class ActivityPub::TagManager if status.account.silenced? # Only notify followers if the account is locally silenced account_ids = status.active_mentions.pluck(:account_id) - to = status.account.followers.where(id: account_ids).map { |account| uri_for(account) } - to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).map { |request| uri_for(request.account) }) + to = status.account.followers.where(id: account_ids).each_with_object([]) do |account, result| + result << uri_for(account) + result << account.followers_url if account.group? + end + to.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result| + result << uri_for(request.account) + result << request.account.followers_url if request.account.group? + end) else - status.active_mentions.map { |mention| uri_for(mention.account) } + status.active_mentions.each_with_object([]) do |mention, result| + result << uri_for(mention.account) + result << mention.account.followers_url if mention.account.group? + end end end end @@ -97,10 +106,19 @@ class ActivityPub::TagManager if status.account.silenced? # Only notify followers if the account is locally silenced account_ids = status.active_mentions.pluck(:account_id) - cc.concat(status.account.followers.where(id: account_ids).map { |account| uri_for(account) }) - cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).map { |request| uri_for(request.account) }) + cc.concat(status.account.followers.where(id: account_ids).each_with_object([]) do |account, result| + result << uri_for(account) + result << account.followers_url if account.group? + end) + cc.concat(FollowRequest.where(target_account_id: status.account_id, account_id: account_ids).each_with_object([]) do |request, result| + result << uri_for(request.account) + result << request.account.followers_url if request.account.group? + end) else - cc.concat(status.active_mentions.map { |mention| uri_for(mention.account) }) + cc.concat(status.active_mentions.each_with_object([]) do |mention, result| + result << uri_for(mention.account) + result << mention.account.followers_url if mention.account.group? + end) end end diff --git a/app/models/account.rb b/app/models/account.rb index d17782f78..884332e5a 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -93,6 +93,7 @@ class Account < ApplicationRecord scope :without_silenced, -> { where(silenced_at: nil) } scope :recent, -> { reorder(id: :desc) } scope :bots, -> { where(actor_type: %w(Application Service)) } + scope :groups, -> { where(actor_type: 'Group') } scope :alphabetic, -> { order(domain: :asc, username: :asc) } scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') } scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) } @@ -153,6 +154,12 @@ class Account < ApplicationRecord self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person' end + def group? + actor_type == 'Group' + end + + alias group group? + def acct local? ? username : "#{username}@#{domain}" end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 17df85de3..aa64936a7 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -49,6 +49,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer 'Application' elsif object.bot? 'Service' + elsif object.group? + 'Group' else 'Person' end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 7e3041ae3..5fec75673 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -3,7 +3,7 @@ class REST::AccountSerializer < ActiveModel::Serializer include RoutingHelper - attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :created_at, + attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at, :note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count, :following_count, :statuses_count, :last_status_at diff --git a/config/locales/en.yml b/config/locales/en.yml index d498f6ce3..f6a14ad1a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -78,6 +78,7 @@ en: roles: admin: Admin bot: Bot + group: Group moderator: Mod unavailable: Profile unavailable unfollow: Unfollow -- cgit