diff options
Diffstat (limited to 'app')
22 files changed, 96 insertions, 70 deletions
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx index 5ad1ca172..b16731c05 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -55,7 +55,8 @@ const textareaStyle = { padding: '10px', fontFamily: 'Roboto', fontSize: '14px', - margin: '0' + margin: '0', + resize: 'vertical' }; const renderInputComponent = inputProps => ( diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx index e6ae25453..41a44e3dc 100644 --- a/app/assets/javascripts/components/locales/en.jsx +++ b/app/assets/javascripts/components/locales/en.jsx @@ -5,9 +5,9 @@ const en = { "status.mention": "Mention", "status.delete": "Delete", "status.reply": "Reply", - "status.reblog": "Reblog", + "status.reblog": "Boost", "status.favourite": "Favourite", - "status.reblogged_by": "{name} reblogged", + "status.reblogged_by": "{name} boosted", "status.sensitive_warning": "Sensitive content", "status.sensitive_toggle": "Click to view", "video_player.toggle_sound": "Toggle sound", @@ -49,7 +49,7 @@ const en = { "upload_form.undo": "Undo", "notification.follow": "{name} followed you", "notification.favourite": "{name} favourited your status", - "notification.reblog": "{name} reblogged your status", + "notification.reblog": "{name} boosted your status", "notification.mention": "{name} mentioned you" }; diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx index 4673305f7..c4458a145 100644 --- a/app/assets/javascripts/components/locales/fr.jsx +++ b/app/assets/javascripts/components/locales/fr.jsx @@ -7,22 +7,24 @@ const fr = { "status.reply": "Répondre", "status.reblog": "Partager", "status.favourite": "Ajouter aux favoris", - "status.reblogged_by": "{name} a partagé", + "status.reblogged_by": "{name} a partagé :", + "status.sensitive_warning": "Contenu délicat", + "status.sensitive_toggle": "Cliquer pour dévoiler", "video_player.toggle_sound": "Mettre/Couper le son", "account.mention": "Mentionner", "account.edit_profile": "Modifier le profil", "account.unblock": "Débloquer", - "account.unfollow": "Se désabonner", + "account.unfollow": "Ne plus suivre", "account.block": "Bloquer", - "account.follow": "S’abonner", + "account.follow": "Suivre", "account.posts": "Statuts", "account.follows": "Abonnements", "account.followers": "Abonnés", "account.follows_you": "Vous suit", "getting_started.heading": "Pour commencer", - "getting_started.about_addressing": "Vous pouvez vous abonner aux statuts de quelqu’un en entrant dans le champs de recherche leur nom d’utilisateur et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.", - "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, le nom d’utilisateur suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.", - "getting_started.about_developer": "Pour s’abonner au développeur de ce projet, c’est Gargron@mastodon.social", + "getting_started.about_addressing": "Vous pouvez vous suivre les statuts de quelqu’un en entrant dans le champs de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.", + "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.", + "getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social", "column.home": "Accueil", "column.mentions": "Mentions", "column.public": "Fil public", @@ -32,21 +34,22 @@ const fr = { "tabs_bar.mentions": "Mentions", "tabs_bar.public": "Public", "tabs_bar.notifications": "Notifications", - "compose_form.placeholder": "Qu’avez vous en tête ?", + "compose_form.placeholder": "Qu’avez-vous en tête ?", "compose_form.publish": "Pouet", + "compose_form.sensitive": "Marquer le contenu comme délicat", "navigation_bar.settings": "Paramètres", "navigation_bar.public_timeline": "Public", - "navigation_bar.logout": "Se déconnecter", + "navigation_bar.logout": "Déconnexion", "reply_indicator.cancel": "Annuler", "search.placeholder": "Chercher", "search.account": "Compte", "search.hashtag": "Mot-clé", "upload_button.label": "Joindre un média", "upload_form.undo": "Annuler", - "notification.follow": "{name} s’est abonné⋅e à vos statuts", - "notification.favourite": "{name} a ajouté votre statut à ses favoris", - "notification.reblog": "{name} a partagé votre statut", - "notification.mention": "{name} vous a mentionné⋅e" + "notification.follow": "{name} vous suit.", + "notification.favourite": "{name} a ajouté à ses favoris :", + "notification.reblog": "{name} a partagé votre statut :", + "notification.mention": "{name} vous a mentionné⋅e :" }; export default fr; diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss index 306f474d6..22b05946e 100644 --- a/app/assets/stylesheets/forms.scss +++ b/app/assets/stylesheets/forms.scss @@ -48,11 +48,16 @@ code { display: block; } - input[type=checkbox] { - display: inline-block; + label.checkbox { position: relative; - top: 3px; - margin-right: 8px; + padding-left: 25px; + } + + input[type=checkbox] { + position: absolute; + left: 0; + top: 1px; + margin: 0; } } diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 5546ee588..ffa8b04fb 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -128,6 +128,6 @@ class Api::V1::AccountsController < ApiController end end - raw.map { |status| cached_keys_with_value[status.cache_key] || uncached[status.id] } + raw.map { |status| cached_keys_with_value[status.cache_key] || uncached[status.id] }.compact end end diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index d74b99a86..b23d7570d 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -39,6 +39,6 @@ class Api::V1::NotificationsController < ApiController end end - raw.map { |notification| cached_keys_with_value[notification.cache_key] || uncached[notification.id] } + raw.map { |notification| cached_keys_with_value[notification.cache_key] || uncached[notification.id] }.compact end end diff --git a/app/controllers/api/v1/timelines_controller.rb b/app/controllers/api/v1/timelines_controller.rb index b1d7c3052..3debbdfc4 100644 --- a/app/controllers/api/v1/timelines_controller.rb +++ b/app/controllers/api/v1/timelines_controller.rb @@ -87,6 +87,6 @@ class Api::V1::TimelinesController < ApiController end end - raw.map { |status| cached_keys_with_value[status.cache_key] || uncached[status.id] } + raw.map { |status| cached_keys_with_value[status.cache_key] || uncached[status.id] }.compact end end diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index a3a2a3275..d2d3bc4a4 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -48,7 +48,7 @@ class ApiController < ApplicationController response.headers['X-RateLimit-Limit'] = match_data[:limit].to_s response.headers['X-RateLimit-Remaining'] = (match_data[:limit] - match_data[:count]).to_s - response.headers['X-RateLimit-Reset'] = (now + (match_data[:period] - now.to_i % match_data[:period])).to_s + response.headers['X-RateLimit-Reset'] = (now + (match_data[:period] - now.to_i % match_data[:period])).iso8601(6) end def set_pagination_headers(next_path = nil, prev_path = nil) diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 5be8719ae..cacc03b65 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -14,7 +14,10 @@ class Settings::PreferencesController < ApplicationController current_user.settings(:notification_emails).favourite = user_params[:notification_emails][:favourite] == '1' current_user.settings(:notification_emails).mention = user_params[:notification_emails][:mention] == '1' - if current_user.update(user_params.except(:notification_emails)) + current_user.settings(:interactions).must_be_follower = user_params[:interactions][:must_be_follower] == '1' + current_user.settings(:interactions).must_be_following = user_params[:interactions][:must_be_following] == '1' + + if current_user.update(user_params.except(:notification_emails, :interactions)) redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg') else render action: :show @@ -24,6 +27,6 @@ class Settings::PreferencesController < ApplicationController private def user_params - params.require(:user).permit(:locale, notification_emails: [:follow, :reblog, :favourite, :mention]) + params.require(:user).permit(:locale, notification_emails: [:follow, :reblog, :favourite, :mention], interactions: [:must_be_follower, :must_be_following]) end end diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index c8512476d..b812ad1f4 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -68,30 +68,34 @@ class FeedManager def filter_from_home?(status, receiver) should_filter = false - if status.reply? && !status.thread.account.nil? # Filter out if it's a reply - should_filter = !receiver.following?(status.thread.account) # and I'm not following the person it's a reply to - should_filter &&= !(receiver.id == status.thread.account_id) # and it's not a reply to me - should_filter &&= !(status.account_id == status.thread.account_id) # and it's not a self-reply - elsif status.reblog? # Filter out a reblog - should_filter = receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person + if status.reply? && !status.thread.account.nil? # Filter out if it's a reply + should_filter = !receiver.following?(status.thread.account) # and I'm not following the person it's a reply to + should_filter &&= !(receiver.id == status.thread.account_id) # and it's not a reply to me + should_filter &&= !(status.account_id == status.thread.account_id) # and it's not a self-reply + elsif status.reblog? # Filter out a reblog + should_filter = receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person end + should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # or if it mentions someone I blocked + should_filter end def filter_from_mentions?(status, receiver) - should_filter = receiver.id == status.account_id # Filter if I'm mentioning myself - should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked + should_filter = receiver.id == status.account_id # Filter if I'm mentioning myself + should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked + should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked - if status.reply? && !status.thread.account.nil? # or it's a reply - should_filter ||= receiver.blocking?(status.thread.account) # to a user I blocked + if status.reply? && !status.thread.account.nil? # or it's a reply + should_filter ||= receiver.blocking?(status.thread.account) # to a user I blocked end should_filter end def filter_from_public?(status, receiver) - should_filter = receiver.blocking?(status.account) + should_filter = receiver.blocking?(status.account) + should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) if status.reply? && !status.thread.account.nil? should_filter ||= receiver.blocking?(status.thread.account) diff --git a/app/models/account.rb b/app/models/account.rb index a60a23e14..65fad2f47 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -66,12 +66,12 @@ class Account < ApplicationRecord def unfollow!(other_account) follow = active_relationships.find_by(target_account: other_account) - follow.destroy unless follow.nil? + follow&.destroy end def unblock!(other_account) block = block_relationships.find_by(target_account: other_account) - block.destroy unless block.nil? + block&.destroy end def following?(other_account) @@ -116,7 +116,11 @@ class Account < ApplicationRecord end def avatar_remote_url=(url) - self.avatar = URI.parse(url) unless self[:avatar_remote_url] == url + parsed_url = URI.parse(url) + + return if !%w(http https).include?(parsed_url.scheme) || self[:avatar_remote_url] == url + + self.avatar = parsed_url self[:avatar_remote_url] = url rescue OpenURI::HTTPError => e Rails.logger.debug "Error fetching remote avatar: #{e}" diff --git a/app/models/status.rb b/app/models/status.rb index 3402929bf..f9dcd97e4 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -97,7 +97,10 @@ class Status < ApplicationRecord end def as_public_timeline(account = nil) - query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id').where('accounts.silenced = FALSE') + query = joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') + .where('accounts.silenced = FALSE') + .where('statuses.in_reply_to_id IS NULL') + .where('statuses.reblog_of_id IS NULL') query = filter_timeline(query, account) unless account.nil? query end @@ -106,6 +109,8 @@ class Status < ApplicationRecord query = tag.statuses .joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id') .where('accounts.silenced = FALSE') + .where('statuses.in_reply_to_id IS NULL') + .where('statuses.reblog_of_id IS NULL') query = filter_timeline(query, account) unless account.nil? query end @@ -123,13 +128,7 @@ class Status < ApplicationRecord def filter_timeline(query, account) blocked = Block.where(account: account).pluck(:target_account_id) return query if blocked.empty? - - query - .joins('LEFT OUTER JOIN statuses AS parents ON statuses.in_reply_to_id = parents.id') - .joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.reblog_of_id = reblogs.id') - .where('statuses.account_id NOT IN (?)', blocked) - .where('(parents.id IS NULL OR parents.account_id NOT IN (?))', blocked) - .where('(reblogs.id IS NULL OR reblogs.account_id NOT IN (?))', blocked) + query.where('statuses.account_id NOT IN (?)', blocked) end end diff --git a/app/models/user.rb b/app/models/user.rb index 366172e9a..423833d47 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -15,6 +15,7 @@ class User < ApplicationRecord has_settings do |s| s.key :notification_emails, defaults: { follow: false, reblog: false, favourite: false, mention: false } + s.key :interactions, defaults: { must_be_follower: false, must_be_following: false } end def send_devise_notification(notification, *args) diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 78301c6ca..40d8a0fee 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -41,14 +41,17 @@ class FanOutOnWriteService < BaseService end def deliver_to_hashtags(status) - Rails.logger.debug "Delivering status #{status.id} to hashtags" + return if status.reblog? || status.reply? + Rails.logger.debug "Delivering status #{status.id} to hashtags" status.tags.find_each do |tag| FeedManager.instance.broadcast("hashtag:#{tag.name}", type: 'update', id: status.id) end end def deliver_to_public(status) + return if status.reblog? || status.reply? + Rails.logger.debug "Delivering status #{status.id} to public timeline" FeedManager.instance.broadcast(:public, type: 'update', id: status.id) end diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb index 37339d8ed..f640222b0 100644 --- a/app/services/follow_remote_account_service.rb +++ b/app/services/follow_remote_account_service.rb @@ -80,8 +80,7 @@ class FollowRemoteAccountService < BaseService end def get_profile(xml, account) - author = xml.at_xpath('/xmlns:feed/xmlns:author') || xml.at_xpath('/xmlns:feed').at_xpath('./dfrn:owner', dfrn: DFRN_NS) - update_remote_profile_service.call(author, account) + update_remote_profile_service.call(xml.at_xpath('/xmlns:feed'), account) end def update_remote_profile_service diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 772adfb90..1efd326b0 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -36,6 +36,8 @@ class NotifyService < BaseService blocked = false blocked ||= @recipient.id == @notification.from_account.id blocked ||= @recipient.blocking?(@notification.from_account) + blocked ||= (@recipient.user.settings(:interactions).must_be_follower && !@notification.from_account.following?(@recipient)) + blocked ||= (@recipient.user.settings(:interactions).must_be_following && !@recipient.following?(@notification.from_account)) blocked ||= send("blocked_#{@notification.type}?") blocked end diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index 1cd801b80..a7a4cb2b0 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -16,7 +16,7 @@ class ProcessFeedService < BaseService def update_author(xml, account) return if xml.at_xpath('/xmlns:feed').nil? - UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed/xmlns:author'), account) + UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed'), account, true) end def process_entries(xml, account) diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index 3bf3471ec..fa14c44da 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -4,7 +4,7 @@ class ProcessHashtagsService < BaseService def call(status, tags = []) tags = status.text.scan(Tag::HASHTAG_RE).map(&:first) if status.local? - tags.map(&:downcase).uniq.each do |tag| + tags.map { |str| str.mb_chars.downcase }.uniq.each do |tag| status.tags << Tag.where(name: tag).first_or_initialize(name: tag) end end diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index e7bb3c73b..3159a4ded 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -26,7 +26,7 @@ class ProcessInteractionService < BaseService end if salmon.verify(envelope, account.keypair) - update_remote_profile_service.call(xml.at_xpath('/xmlns:entry/xmlns:author'), account) + update_remote_profile_service.call(xml.at_xpath('/xmlns:entry'), account, true) case verb(xml) when :follow diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 598c7d02c..1ae1d5a80 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -2,9 +2,9 @@ class SearchService < BaseService def call(query, limit, resolve = false) - return if query.blank? + return if query.blank? || query.start_with?('#') - username, domain = query.split('@') + username, domain = query.gsub(/\A@/, '').split('@') results = if domain.nil? Account.search_for(username) diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index 2909ae12a..26ab84d75 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -2,24 +2,22 @@ class UpdateRemoteProfileService < BaseService POCO_NS = 'http://portablecontacts.net/spec/1.0' + DFRN_NS = 'http://purl.org/macgirvin/dfrn/1.0' - def call(author_xml, account) - return if author_xml.nil? + def call(xml, account, resubscribe = false) + author_xml = xml.at_xpath('./xmlns:author') || xml.at_xpath('./dfrn:owner', dfrn: DFRN_NS) + hub_link = xml.at_xpath('./xmlns:link[@rel="hub"]') - account.display_name = if author_xml.at_xpath('./poco:displayName', poco: POCO_NS).nil? - account.username - else - author_xml.at_xpath('./poco:displayName', poco: POCO_NS).content - end - - unless author_xml.at_xpath('./poco:note').nil? - account.note = author_xml.at_xpath('./poco:note', poco: POCO_NS).content - end - - unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]').nil? - account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]').attribute('href').value + unless author_xml.nil? + account.display_name = author_xml.at_xpath('./poco:displayName', poco: POCO_NS).content unless author_xml.at_xpath('./poco:displayName', poco: POCO_NS).nil? + account.note = author_xml.at_xpath('./poco:note', poco: POCO_NS).content unless author_xml.at_xpath('./poco:note').nil? + account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]')['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]').nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]')['href'].blank? end + old_hub_url = account.hub_url + account.hub_url = hub_link['href'] if !hub_link.nil? && !hub_link['href'].blank? && (hub_link['href'] != old_hub_url) account.save! + + SubscribeService.new.call(account) if resubscribe && (account.hub_url != old_hub_url) end end diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index 693702ff7..db5b9fb48 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -12,6 +12,10 @@ = ff.input :favourite, as: :boolean, wrapper: :with_label = ff.input :mention, as: :boolean, wrapper: :with_label + = f.simple_fields_for :interactions, current_user.settings(:interactions) do |ff| + = ff.input :must_be_follower, as: :boolean, wrapper: :with_label + = ff.input :must_be_following, as: :boolean, wrapper: :with_label + .actions = f.button :button, t('generic.save_changes'), type: :submit |