From: Thibaut Girka Date: Thu, 10 Jan 2019 18:12:10 +0000 (+0100) Subject: Merge branch 'master' into glitch-soc/merge-upstream X-Git-Url: https://git.xn--scling-oua.cat.family/?a=commitdiff_plain;h=a2a64ecd3e3551707412c47f0d16e484dea25632;p=mastodon.git Merge branch 'master' into glitch-soc/merge-upstream Conflicts: - .eslintrc.yml Removed, as upstream removed it. - app/controllers/admin/statuses_controller.rb Minor code cleanup when porting one of our features. - app/models/account.rb Note length validation has changed upstream. We now use upstream's validation (dropped legacy glitch-soc account metadata stuff) but with configurable limit. - app/services/post_status_service.rb Upstream has added support for scheduled toots, refactoring the code a bit. Adapted our changes to this refactoring. - app/views/stream_entries/_detailed_status.html.haml Not a real conflict, changes too close. - app/views/stream_entries/_simple_status.html.haml Not a real conflict, changes too close. --- a2a64ecd3e3551707412c47f0d16e484dea25632 diff --cc app/controllers/remote_interaction_controller.rb index 6861f3f21,cc6993c52..d7197d434 --- a/app/controllers/remote_interaction_controller.rb +++ b/app/controllers/remote_interaction_controller.rb @@@ -5,9 -5,9 +5,10 @@@ class RemoteInteractionController < App layout 'modal' + before_action :set_interaction_type before_action :set_status before_action :set_body_classes + before_action :set_pack def new @remote_follow = RemoteFollow.new(session_params) @@@ -47,7 -47,7 +48,11 @@@ @hide_header = true end + def set_pack + use_pack 'modal' + end ++ + def set_interaction_type + @interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply' + end end diff --cc app/javascript/packs/public.js index 47c4bf75c,a9a3d738a..196d2d02f --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@@ -68,16 -102,104 +85,7 @@@ function main() if (parallaxComponents.length > 0 ) { new Rellax('.parallax', { speed: -1 }); } - - const history = createHistory(); - const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status'); - const location = history.location; - - if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) { - detailedStatuses[0].scrollIntoView(); - history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true }); - } }); - - delegate(document, '.webapp-btn', 'click', ({ target, button }) => { - if (button !== 0) { - return true; - } - window.location.href = target.href; - return false; - }); - - delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => { - const contentEl = target.parentNode.parentNode.querySelector('.e-content'); - - if (contentEl.style.display === 'block') { - contentEl.style.display = 'none'; - target.parentNode.style.marginBottom = 0; - } else { - contentEl.style.display = 'block'; - target.parentNode.style.marginBottom = null; - } - - return false; - }); - - delegate(document, '.modal-button', 'click', e => { - e.preventDefault(); - - let href; - - if (e.target.nodeName !== 'A') { - href = e.target.parentNode.href; - } else { - href = e.target.href; - } - - window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); - }); - - delegate(document, '#account_display_name', 'input', ({ target }) => { - const name = document.querySelector('.card .display-name strong'); - if (name) { - if (target.value) { - name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); - } else { - name.textContent = document.querySelector('#default_account_display_name').textContent; - } - } - }); - - delegate(document, '#account_avatar', 'change', ({ target }) => { - const avatar = document.querySelector('.card .avatar img'); - const [file] = target.files || []; - const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; - - avatar.src = url; - }); - - delegate(document, '#account_header', 'change', ({ target }) => { - const header = document.querySelector('.card .card__img img'); - const [file] = target.files || []; - const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc; - - header.src = url; - }); - - delegate(document, '#account_locked', 'change', ({ target }) => { - const lock = document.querySelector('.card .display-name i'); - - if (target.checked) { - lock.style.display = 'inline'; - } else { - lock.style.display = 'none'; - } - }); - - delegate(document, '.input-copy input', 'click', ({ target }) => { - target.select(); - }); - - delegate(document, '.input-copy button', 'click', ({ target }) => { - const input = target.parentNode.querySelector('.input-copy__wrapper input'); - - input.focus(); - input.select(); - - try { - if (document.execCommand('copy')) { - input.blur(); - target.parentNode.classList.add('copied'); - - setTimeout(() => { - target.parentNode.classList.remove('copied'); - }, 700); - } - } catch (err) { - console.error(err); - } - }); } loadPolyfills().then(main).catch(error => { diff --cc app/models/account.rb index 67d9a583e,11a3c21fe..1ee63c738 --- a/app/models/account.rb +++ b/app/models/account.rb @@@ -78,9 -74,9 +78,9 @@@ class Account < ApplicationRecor validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? } validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? } - validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? } - validates :note, note_length: { maximum: 160 }, if: -> { local? && will_save_change_to_note? } - validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? } + validates :display_name, length: { maximum: MAX_DISPLAY_NAME_LENGTH }, if: -> { local? && will_save_change_to_display_name? } - validate :note_length_does_not_exceed_length_limit, if: -> { local? && will_save_change_to_note? } ++ validates :note, note_length: { maximum: MAX_NOTE_LENGTH }, if: -> { local? && will_save_change_to_note? } + validates :fields, length: { maximum: MAX_FIELDS }, if: -> { local? && will_save_change_to_fields? } scope :remote, -> { where.not(domain: nil) } scope :local, -> { where(domain: nil) } diff --cc app/services/post_status_service.rb index 28ecc848d,260765edf..2ca92dc50 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@@ -13,65 -17,80 +17,85 @@@ class PostStatusService < BaseServic # @option [Doorkeeper::Application] :application # @option [String] :idempotency Optional idempotency key # @return [Status] - def call(account, text, in_reply_to = nil, **options) - if options[:idempotency].present? - existing_id = redis.get("idempotency:status:#{account.id}:#{options[:idempotency]}") - return Status.find(existing_id) if existing_id + def call(account, options = {}) + @account = account + @options = options + @text = @options[:text] || '' + @in_reply_to = @options[:thread] + + return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + + validate_media! + preprocess_attributes! + + if scheduled? + schedule_status! + else + process_status! + postprocess_status! + bump_potential_friendship! end - media = validate_media!(options[:media_ids]) - status = nil - if text.blank? && options[:spoiler_text].present? - text = '.' - text = media.find(&:video?) ? '📹' : '🖼' if media.size > 0 + redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given? + + @status + end + + private + + def preprocess_attributes! - @text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present? ++ if @text.blank? && @options[:spoiler_text].present? ++ @text = '.' ++ @text = @media.find(&:video?) ? '📹' : '🖼' if @media.size > 0 + end + @visibility = @options[:visibility] || @account.user&.setting_default_privacy + @visibility = :unlisted if @visibility == :public && @account.silenced + @scheduled_at = @options[:scheduled_at]&.to_datetime + @scheduled_at = nil if scheduled_in_the_past? + end - visibility = options[:visibility] || account.user&.setting_default_privacy - visibility = :unlisted if visibility == :public && account.silenced + def process_status! + # The following transaction block is needed to wrap the UPDATEs to + # the media attachments when the status is created ApplicationRecord.transaction do - status = account.statuses.create!(text: text, - media_attachments: media || [], - thread: in_reply_to, - sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]) || options[:spoiler_text].present?, - spoiler_text: options[:spoiler_text] || '', - visibility: visibility, - language: language_from_option(options[:language]) || account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(text, account), - application: options[:application]) + @status = @account.statuses.create!(status_attributes) end - process_hashtags_service.call(status) - process_mentions_service.call(status) + process_hashtags_service.call(@status) + process_mentions_service.call(@status) + end - LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text? - DistributionWorker.perform_async(status.id) + def schedule_status! + if @account.statuses.build(status_attributes).valid? + # The following transaction block is needed to wrap the UPDATEs to + # the media attachments when the scheduled status is created - unless status.local_only? - Pubsubhubbub::DistributionWorker.perform_async(status.stream_entry.id) - ActivityPub::DistributionWorker.perform_async(status.id) + ApplicationRecord.transaction do + @status = @account.scheduled_statuses.create!(scheduled_status_attributes) + end + else + raise ActiveRecord::RecordInvalid end + end - if options[:idempotency].present? - redis.setex("idempotency:status:#{account.id}:#{options[:idempotency]}", 3_600, status.id) + def postprocess_status! + LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text? + DistributionWorker.perform_async(@status.id) - Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id) - ActivityPub::DistributionWorker.perform_async(@status.id) ++ unless @status.local_only? ++ Pubsubhubbub::DistributionWorker.perform_async(@status.stream_entry.id) ++ ActivityPub::DistributionWorker.perform_async(@status.id) + end - - bump_potential_friendship(account, status) - - status end - private - - def validate_media!(media_ids) - return if media_ids.blank? || !media_ids.is_a?(Enumerable) + def validate_media! + return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) - raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4 + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 - media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i)) + @media = MediaAttachment.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) - raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media.size > 1 && media.find(&:video?) - - media + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?) end def language_from_option(str) diff --cc app/views/stream_entries/_detailed_status.html.haml index 447667c5a,41d4714b9..9298ecbb0 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@@ -43,11 -43,8 +43,11 @@@ - else = link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener' · - = link_to remote_interaction_path(status), class: 'modal-button detailed-status__link' do + = link_to remote_interaction_path(status, type: :reply), class: 'modal-button detailed-status__link' do - = fa_icon('reply') + - if status.in_reply_to_id.nil? + = fa_icon('reply') + - else + = fa_icon('reply-all') %span.detailed-status__reblogs>= number_to_human status.replies_count, strip_insignificant_zeros: true = " " · diff --cc app/views/stream_entries/_simple_status.html.haml index 3a4dacc06,89a6fe048..1d44be791 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@@ -24,10 -24,9 +24,10 @@@ %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}  %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') - .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) + .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< + = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - - unless status.media_attachments.empty? + - if !status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description @@@ -36,13 -37,10 +38,13 @@@ .status__action-bar .status__action-bar__counter - = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do - = fa_icon 'reply fw' + - if status.in_reply_to_id.nil? + = fa_icon 'reply fw' + - else + = fa_icon 'reply-all fw' .status__action-bar__counter__label= obscured_counter status.replies_count - = link_to remote_interaction_path(status), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do - if status.public_visibility? || status.unlisted_visibility? = fa_icon 'retweet fw' - elsif status.private_visibility? diff --cc config/locales/sk.yml index 31f735e19,91d1fdb8f..d038d5a06 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@@ -824,8 -825,21 +825,20 @@@ sk + +
+ +

title: Podmienky užívania, a pravidlá súkromia pre %{instance} themes: - contrast: Vysoký kontrast default: Mastodon mastodon-light: Mastodon (svetlý) time: diff --cc config/settings.yml index bd6578bd4,4f7c2c8f3..4f070240a --- a/config/settings.yml +++ b/config/settings.yml @@@ -1,13 -1,8 +1,8 @@@ - # config/app.yml for rails-settings-cached - # - # This file contains default values, and does not need to be edited - # when configuring an instance. These settings may be changed by an - # Administrator using the Web UI. - # - # For more information, see docs/Running-Mastodon/Administration-guide.md - # + # This file contains default values, and does not need to be edited. All + # important settings can be changed from the admin interface. + defaults: &defaults - site_title: Mastodon + site_title: 'dev.glitch.social' site_short_description: '' site_description: '' site_extended_description: '' diff --cc spec/lib/feed_manager_spec.rb index a56158f12,c506cd87f..df92094d1 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@@ -119,13 -119,6 +119,13 @@@ RSpec.describe FeedManager d expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true end + it 'returns true for status by followee mentioning muted account' do + bob.mute!(jeff) + bob.follow!(alice) - status = PostStatusService.new.call(alice, 'Hey @jeff') ++ status = PostStatusService.new.call(alice, text: 'Hey @jeff') + expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true + end + it 'returns true for reblog of a personally blocked domain' do alice.block_domain!('example.com') alice.follow!(jeff)