- {!onCloseVideo &&
}
+ {!onCloseVideo &&
}
{(!fullscreen && onOpenVideo) &&
}
{onCloseVideo &&
}
diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json
index f98ea7f26a..cbf303f3ca 100644
--- a/app/javascript/mastodon/locales/cs.json
+++ b/app/javascript/mastodon/locales/cs.json
@@ -77,6 +77,7 @@
"compose_form.poll.remove_option": "Odstranit tuto volbu",
"compose_form.publish": "Tootnout",
"compose_form.publish_loud": "{publish}!",
+ "compose_form.sensitive.hide": "Označit média jako citlivá",
"compose_form.sensitive.marked": "Média jsou označena jako citlivá",
"compose_form.sensitive.unmarked": "Média nejsou označena jako citlivá",
"compose_form.spoiler.marked": "Text je skrytý za varováním",
@@ -209,6 +210,7 @@
"lightbox.close": "Zavřít",
"lightbox.next": "Další",
"lightbox.previous": "Předchozí",
+ "lightbox.view_context": "Zobrazit kontext",
"lists.account.add": "Přidat do seznamu",
"lists.account.remove": "Odebrat ze seznamu",
"lists.delete": "Smazat seznam",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index d155619c9f..ca7732d851 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -243,7 +243,7 @@
"navigation_bar.pins": "Ամրացված թթեր",
"navigation_bar.preferences": "Նախապատվություններ",
"navigation_bar.public_timeline": "Դաշնային հոսք",
- "navigation_bar.security": "Security",
+ "navigation_bar.security": "Անվտանգություն",
"notification.favourite": "{name} հավանեց թութդ",
"notification.follow": "{name} սկսեց հետեւել քեզ",
"notification.mention": "{name} նշեց քեզ",
@@ -309,7 +309,7 @@
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
- "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "search_results.total": "{count, number} {count, plural, one {արդյունք} other {արդյունք}}",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Արգելափակել @{name}֊ին",
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 05c7821e44..74f91599a6 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -50,6 +50,7 @@ $content-width: 840px;
color: $darker-text-color;
text-decoration: none;
transition: all 200ms linear;
+ transition-property: color, background-color;
border-radius: 4px 0 0 4px;
i.fa {
@@ -60,6 +61,7 @@ $content-width: 840px;
color: $primary-text-color;
background-color: darken($ui-base-color, 5%);
transition: all 100ms linear;
+ transition-property: color, background-color;
}
&.selected {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 7ea4ad07c4..1ba6fa5b1d 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -264,6 +264,16 @@
.compose-form {
padding: 10px;
+ &__sensitive-button {
+ padding: 10px;
+ padding-top: 0;
+
+ .icon-button {
+ font-size: 14px;
+ font-weight: 500;
+ }
+ }
+
.compose-form__warning {
color: $inverted-text-color;
margin-bottom: 10px;
@@ -1962,6 +1972,7 @@ a.account__display-name {
font-weight: 500;
border-bottom: 2px solid lighten($ui-base-color, 8%);
transition: all 50ms linear;
+ transition-property: border-bottom, background, color;
.fa {
font-weight: 400;
@@ -2127,7 +2138,7 @@ a.account__display-name {
padding: 0;
border-radius: 30px;
background-color: $ui-base-color;
- transition: all 0.2s ease;
+ transition: background-color 0.2s ease;
}
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
@@ -2180,7 +2191,6 @@ a.account__display-name {
}
.react-toggle-thumb {
- transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
position: absolute;
top: 1px;
left: 1px;
@@ -2191,6 +2201,7 @@ a.account__display-name {
background-color: darken($simple-background-color, 2%);
box-sizing: border-box;
transition: all 0.25s ease;
+ transition-property: border-color, left;
}
.react-toggle--checked .react-toggle-thumb {
@@ -2412,7 +2423,7 @@ a.account__display-name {
& > div {
background: rgba($base-shadow-color, 0.6);
- border-radius: 4px;
+ border-radius: 8px;
padding: 12px 9px;
flex: 0 0 auto;
display: flex;
@@ -2423,19 +2434,18 @@ a.account__display-name {
button,
a {
display: inline;
- color: $primary-text-color;
+ color: $secondary-text-color;
background: transparent;
border: 0;
- padding: 0 5px;
+ padding: 0 8px;
text-decoration: none;
- opacity: 0.6;
font-size: 18px;
line-height: 18px;
&:hover,
&:active,
&:focus {
- opacity: 1;
+ color: $primary-text-color;
}
}
@@ -2932,15 +2942,49 @@ a.status-card.compact:hover {
}
.spoiler-button {
- display: none;
- left: 4px;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
position: absolute;
- text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
- top: 4px;
z-index: 100;
- &.spoiler-button--visible {
+ &--minified {
display: block;
+ left: 4px;
+ top: 4px;
+ width: auto;
+ height: auto;
+ }
+
+ &--hidden {
+ display: none;
+ }
+
+ &__overlay {
+ display: block;
+ background: transparent;
+ width: 100%;
+ height: 100%;
+ border: 0;
+
+ &__label {
+ display: inline-block;
+ background: rgba($base-overlay-background, 0.5);
+ border-radius: 8px;
+ padding: 8px 12px;
+ color: $primary-text-color;
+ font-weight: 500;
+ font-size: 14px;
+ }
+
+ &:hover,
+ &:focus,
+ &:active {
+ .spoiler-button__overlay__label {
+ background: rgba($base-overlay-background, 0.8);
+ }
+ }
}
}
@@ -3509,6 +3553,7 @@ a.status-card.compact:hover {
display: inline-block;
opacity: 0;
transition: all 100ms linear;
+ transition-property: transform, opacity;
font-size: 18px;
width: 18px;
height: 18px;
@@ -3728,6 +3773,31 @@ a.status-card.compact:hover {
pointer-events: none;
}
+.media-modal__meta {
+ text-align: center;
+ position: absolute;
+ left: 0;
+ bottom: 20px;
+ width: 100%;
+ pointer-events: none;
+
+ &--shifted {
+ bottom: 62px;
+ }
+
+ a {
+ text-decoration: none;
+ font-weight: 500;
+ color: $ui-secondary-color;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
+}
+
.media-modal__page-dot {
display: inline-block;
}
@@ -4200,6 +4270,7 @@ a.status-card.compact:hover {
pointer-events: none;
opacity: 0.9;
transition: opacity 0.1s ease;
+ line-height: 18px;
}
.media-gallery__gifv {
@@ -4313,6 +4384,8 @@ a.status-card.compact:hover {
text-decoration: none;
color: $secondary-text-color;
line-height: 0;
+ position: relative;
+ z-index: 1;
&,
img {
@@ -4325,6 +4398,21 @@ a.status-card.compact:hover {
}
}
+.media-gallery__preview {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 0;
+ background: $base-overlay-background;
+
+ &--hidden {
+ display: none;
+ }
+}
+
.media-gallery__gifv {
height: 100%;
overflow: hidden;
@@ -4620,6 +4708,23 @@ a.status-card.compact:hover {
}
}
+ &__link {
+ padding: 2px 10px;
+
+ a {
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 500;
+ color: $white;
+
+ &:hover,
+ &:active,
+ &:focus {
+ text-decoration: underline;
+ }
+ }
+ }
+
&__seek {
cursor: pointer;
height: 24px;
@@ -4712,62 +4817,18 @@ a.status-card.compact:hover {
.account-gallery__container {
display: flex;
- justify-content: center;
flex-wrap: wrap;
- padding: 2px;
+ padding: 4px 2px;
}
.account-gallery__item {
- flex-grow: 1;
- width: 50%;
- overflow: hidden;
+ border: none;
+ box-sizing: border-box;
+ display: block;
position: relative;
-
- &::before {
- content: "";
- display: block;
- padding-top: 100%;
- }
-
- a {
- display: block;
- width: calc(100% - 4px);
- height: calc(100% - 4px);
- margin: 2px;
- top: 0;
- left: 0;
- background-color: $base-overlay-background;
- background-size: cover;
- background-position: center;
- position: absolute;
- color: $darker-text-color;
- text-decoration: none;
- border-radius: 4px;
-
- &:hover,
- &:active,
- &:focus {
- outline: 0;
- color: $secondary-text-color;
-
- &::before {
- content: "";
- display: block;
- width: 100%;
- height: 100%;
- background: rgba($base-overlay-background, 0.3);
- border-radius: 4px;
- }
- }
- }
-
- &__icons {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 24px;
- }
+ border-radius: 4px;
+ overflow: hidden;
+ margin: 2px;
}
.notification__filter-bar,
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 91888d3059..2b8d7a6825 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -533,6 +533,17 @@ code {
color: $error-value-color;
}
+ a {
+ display: inline-block;
+ color: $darker-text-color;
+ text-decoration: none;
+
+ &:hover {
+ color: $primary-text-color;
+ text-decoration: underline;
+ }
+ }
+
p {
margin-bottom: 15px;
}
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index e736d7a7ef..acaf5b0240 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -4,7 +4,6 @@
&__img {
width: 100%;
- height: 167px;
position: relative;
overflow: hidden;
border-radius: 4px 4px 0 0;
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index dabdcbcf7b..6b16c99863 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -194,7 +194,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
next if attachment['url'].blank?
href = Addressable::URI.parse(attachment['url']).normalize.to_s
- media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'])
+ media_attachment = MediaAttachment.create(account: @account, remote_url: href, description: attachment['name'].presence, focus: attachment['focalPoint'], blurhash: supported_blurhash?(attachment['blurhash']) ? attachment['blurhash'] : nil)
media_attachments << media_attachment
next if unsupported_media_type?(attachment['mediaType']) || skip_download?
@@ -369,6 +369,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type)
end
+ def supported_blurhash?(blurhash)
+ components = blurhash.blank? ? nil : Blurhash.components(blurhash)
+ components.present? && components.none? { |comp| comp > 5 }
+ end
+
def skip_download?
return @skip_download if defined?(@skip_download)
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media?
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 94eb2899c1..c259c96f41 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -19,6 +19,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
+ blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
}.freeze
def self.default_key_transform
diff --git a/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb
index e1b5e3832b..84ff84c4b0 100644
--- a/app/models/concerns/ldap_authenticable.rb
+++ b/app/models/concerns/ldap_authenticable.rb
@@ -6,6 +6,7 @@ module LdapAuthenticable
def ldap_setup(_attributes)
self.confirmed_at = Time.now.utc
self.admin = false
+ self.external = true
save!
end
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index 1b28b8162e..2830330839 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -66,6 +66,7 @@ module Omniauthable
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0, 20],
agreement: true,
+ external: true,
account_attributes: {
username: ensure_unique_username(auth.uid),
display_name: display_name,
diff --git a/app/models/concerns/pam_authenticable.rb b/app/models/concerns/pam_authenticable.rb
index 2f651c1a35..6169d4dfaa 100644
--- a/app/models/concerns/pam_authenticable.rb
+++ b/app/models/concerns/pam_authenticable.rb
@@ -34,6 +34,7 @@ module PamAuthenticable
self.confirmed_at = Time.now.utc
self.admin = false
self.account = account
+ self.external = true
account.destroy! unless save
end
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 069cda3673..0b12617c6c 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -29,4 +29,11 @@ class DomainBlock < ApplicationRecord
def self.blocked?(domain)
where(domain: domain, severity: :suspend).exists?
end
+
+ def stricter_than?(other_block)
+ return true if suspend?
+ return false if other_block.suspend? && (silence? || noop?)
+ return false if other_block.silence? && noop?
+ (reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
+ end
end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 81397a18e3..65f00e1c86 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -18,6 +18,7 @@
# account_id :bigint(8)
# description :text
# scheduled_status_id :bigint(8)
+# blurhash :string
#
class MediaAttachment < ApplicationRecord
@@ -34,6 +35,11 @@ class MediaAttachment < ApplicationRecord
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze
+ BLURHASH_OPTIONS = {
+ x_comp: 4,
+ y_comp: 4,
+ }.freeze
+
IMAGE_STYLES = {
original: {
pixels: 1_638_400, # 1280x1280px
@@ -43,6 +49,7 @@ class MediaAttachment < ApplicationRecord
small: {
pixels: 160_000, # 400x400px
file_geometry_parser: FastGeometryParser,
+ blurhash: BLURHASH_OPTIONS,
},
}.freeze
@@ -71,6 +78,8 @@ class MediaAttachment < ApplicationRecord
},
format: 'png',
time: 0,
+ file_geometry_parser: FastGeometryParser,
+ blurhash: BLURHASH_OPTIONS,
},
}.freeze
@@ -186,13 +195,13 @@ class MediaAttachment < ApplicationRecord
def file_processors(f)
if f.file_content_type == 'image/gif'
- [:gif_transcoder]
+ [:gif_transcoder, :blurhash_transcoder]
elsif VIDEO_MIME_TYPES.include? f.file_content_type
- [:video_transcoder]
+ [:video_transcoder, :blurhash_transcoder]
elsif AUDIO_MIME_TYPES.include? f.file_content_type
[:audio_transcoder]
else
- [:lazy_thumbnail]
+ [:lazy_thumbnail, :blurhash_transcoder]
end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index b2fb820af2..77e6a33b5f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -78,7 +78,7 @@ class User < ApplicationRecord
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
- validates_with BlacklistedEmailValidator, if: :email_changed?
+ validates_with BlacklistedEmailValidator, on: :create
validates_with EmailMxValidator, if: :validate_email_dns?
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
@@ -107,13 +107,14 @@ class User < ApplicationRecord
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application, to: :settings, prefix: :setting, allow_nil: false
attr_reader :invite_code
+ attr_writer :external
def confirmed?
confirmed_at.present?
end
def invited?
- invite_id.present?
+ invite_id.present? && invite.valid_for_use?
end
def disable!
@@ -273,13 +274,17 @@ class User < ApplicationRecord
private
def set_approved
- self.approved = open_registrations? || invited?
+ self.approved = open_registrations? || invited? || external?
end
def open_registrations?
Setting.registrations_mode == 'open'
end
+ def external?
+ !!@external
+ end
+
def sanitize_languages
return if chosen_languages.nil?
chosen_languages.reject!(&:blank?)
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index d11cfa59ae..67f596e78a 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -2,7 +2,7 @@
class ActivityPub::NoteSerializer < ActivityPub::Serializer
context_extensions :atom_uri, :conversation, :sensitive,
- :hashtag, :emoji, :focal_point
+ :hashtag, :emoji, :focal_point, :blurhash
attributes :id, :type, :summary,
:in_reply_to, :published, :url,
@@ -153,7 +153,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
class MediaAttachmentSerializer < ActivityPub::Serializer
include RoutingHelper
- attributes :type, :media_type, :url, :name
+ attributes :type, :media_type, :url, :name, :blurhash
attribute :focal_point, if: :focal_point?
def type
diff --git a/app/serializers/rest/media_attachment_serializer.rb b/app/serializers/rest/media_attachment_serializer.rb
index 51011788ba..1b3498ea4e 100644
--- a/app/serializers/rest/media_attachment_serializer.rb
+++ b/app/serializers/rest/media_attachment_serializer.rb
@@ -5,7 +5,7 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
attributes :id, :type, :url, :preview_url,
:remote_url, :text_url, :meta,
- :description
+ :description, :blurhash
def id
object.id.to_s
diff --git a/app/services/block_service.rb b/app/services/block_service.rb
index 140b238df3..10ed470e0a 100644
--- a/app/services/block_service.rb
+++ b/app/services/block_service.rb
@@ -6,6 +6,7 @@ class BlockService < BaseService
UnfollowService.new.call(account, target_account) if account.following?(target_account)
UnfollowService.new.call(target_account, account) if target_account.following?(account)
+ RejectFollowService.new.call(account, target_account) if target_account.requested?(account)
block = account.block!(target_account)
diff --git a/app/validators/blacklisted_email_validator.rb b/app/validators/blacklisted_email_validator.rb
index a2061fdd31..a288c20ef4 100644
--- a/app/validators/blacklisted_email_validator.rb
+++ b/app/validators/blacklisted_email_validator.rb
@@ -2,7 +2,10 @@
class BlacklistedEmailValidator < ActiveModel::Validator
def validate(user)
+ return if user.invited?
+
@email = user.email
+
user.errors.add(:email, I18n.t('users.invalid_email')) if blocked_email?
end
@@ -13,7 +16,7 @@ class BlacklistedEmailValidator < ActiveModel::Validator
end
def on_blacklist?
- return true if EmailDomainBlock.block?(@email)
+ return true if EmailDomainBlock.block?(@email)
return false if Rails.configuration.x.email_domains_blacklist.blank?
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 4459581d94..23f2920d89 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -28,7 +28,7 @@
- elsif !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: 670, height: 380, detailed: true, inline: true, alt: video.description do
+ = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
- else
= react_component :media_gallery, height: 380, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 6d2a408ea3..4df1a0cdfe 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -33,7 +33,7 @@
- elsif !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 do
+ = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }
- else
= react_component :media_gallery, height: 343, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, 'autoPlayGif': current_account&.user&.setting_auto_play_gif || autoplay, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } do
diff --git a/app/workers/activitypub/processing_worker.rb b/app/workers/activitypub/processing_worker.rb
index a3abe72cf6..05139f616d 100644
--- a/app/workers/activitypub/processing_worker.rb
+++ b/app/workers/activitypub/processing_worker.rb
@@ -7,5 +7,7 @@ class ActivityPub::ProcessingWorker
def perform(account_id, body, delivered_to_account_id = nil)
ActivityPub::ProcessCollectionService.new.call(body, Account.find(account_id), override_timestamps: true, delivered_to_account_id: delivered_to_account_id, delivery: true)
+ rescue ActiveRecord::RecordInvalid => e
+ Rails.logger.debug "Error processing incoming ActivityPub object: #{e}"
end
end
diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb
index 2ddbfb99ce..c30bd8a643 100644
--- a/config/initializers/rack_attack_logging.rb
+++ b/config/initializers/rack_attack_logging.rb
@@ -1,4 +1,6 @@
-ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start, _finish, _request_id, req|
+ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload|
+ req = payload[:request]
+
next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type']
Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}")
end
diff --git a/config/locales/co.yml b/config/locales/co.yml
index aa68336f16..8c1a13e54b 100644
--- a/config/locales/co.yml
+++ b/config/locales/co.yml
@@ -269,6 +269,7 @@ co:
created_msg: U blucchime di u duminiu hè attivu
destroyed_msg: U blucchime di u duminiu ùn hè più attivu
domain: Duminiu
+ existing_domain_block_html: Avete digià impostu limite più strette nant'à %{name}, duvete
sbluccallu primu.
new:
create: Creà un blucchime
hint: U blucchime di duminiu ùn impedirà micca a creazione di conti indè a database, mà metudi di muderazione specifiche saranu applicati.
diff --git a/config/locales/cs.yml b/config/locales/cs.yml
index ca456b7efb..5d05a13d68 100644
--- a/config/locales/cs.yml
+++ b/config/locales/cs.yml
@@ -273,6 +273,7 @@ cs:
created_msg: Blokace domény se právě vyřizuje
destroyed_msg: Blokace domény byla zrušena
domain: Doména
+ existing_domain_block_html: Pro účet %{name} jste již nastavil/a přísnější omezení, musíte jej nejdříve
odblokovat.
new:
create: Vytvořit blokaci
hint: Blokace domény nezakáže vytváření záznamů účtů v databázi, ale bude na tyto účty zpětně a automaticky aplikovat specifické metody moderování.
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 20a75e60ce..5f87b34d6a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -270,6 +270,7 @@ en:
created_msg: Domain block is now being processed
destroyed_msg: Domain block has been undone
domain: Domain
+ existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to
unblock it first.
new:
create: Create block
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index a6c806de39..d588b239ff 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -260,10 +260,10 @@ fr:
title: Nouveau blocage de domaine
reject_media: Fichiers média rejetés
reject_media_hint: Supprime localement les fichiers média stockés et refuse d’en télécharger ultérieurement. Ne concerne pas les suspensions
- reject_reports: Rapports de rejet
- reject_reports_hint: Ignorez tous les rapports provenant de ce domaine. Sans objet pour les suspensions
+ reject_reports: Rejeter les signalements
+ reject_reports_hint: Ignorez tous les signalements provenant de ce domaine. Ne concerne pas les suspensions
rejecting_media: rejet des fichiers multimédia
- rejecting_reports: rejet de rapports
+ rejecting_reports: rejet des signalements
severity:
silence: silencié
suspend: suspendu
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index d3580c9819..d859d16b1e 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -268,10 +268,11 @@ sk:
week_users_active: aktívni tento týždeň
week_users_new: užívateľov počas tohto týždňa
domain_blocks:
- add_new: Pridaj nové doménové blokovanie
+ add_new: Blokuj novú doménu
created_msg: Doména je v štádiu blokovania
destroyed_msg: Blokovanie domény bolo zrušené
domain: Doména
+ existing_domain_block_html: Pre účet %{name} si už nahodil/a přísnejšie obmedzenie, najskôr ho teda musíš
odblokovať.
new:
create: Vytvor blokovanie domény
hint: Blokovanie domény stále dovolí vytvárať nové účty v databázi, ale tieto budú spätne automaticky moderované.
@@ -299,7 +300,7 @@ sk:
silence: Zruš stíšenie všetkých existujúcich účtov z tejto domény
suspend: Zruš suspendáciu všetkých existujúcich účtov z tejto domény
title: Zruš blokovanie domény %{domain}
- undo: Vrátiť späť
+ undo: Vráť späť
undo: Odvolaj blokovanie domény
email_domain_blocks:
add_new: Pridaj nový
@@ -527,16 +528,17 @@ sk:
login: Prihlás sa
logout: Odhlás sa
migrate_account: Presúvam sa na iný účet
- migrate_account_html: Pokiaľ si želáš presmerovať tento účet na nejaký iný, môžeš si to
nastaviť tu.
- or_log_in_with: Alebo prihlásiť z
+ migrate_account_html: Ak si želáš presmerovať tento účet na nejaký iný, môžeš si to
nastaviť tu.
+ or_log_in_with: Alebo prihlás s
providers:
cas: CAS
saml: SAML
register: Zaregistruj sa
- resend_confirmation: Poslať potvrdzujúce pokyny znovu
+ resend_confirmation: Zašli potvrdzujúce pokyny znovu
reset_password: Obnov heslo
security: Zabezpečenie
set_new_password: Nastav nové heslo
+ trouble_logging_in: Problém s prihlásením?
authorize_follow:
already_following: Tento účet už následuješ
error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu
diff --git a/db/migrate/20190420025523_add_blurhash_to_media_attachments.rb b/db/migrate/20190420025523_add_blurhash_to_media_attachments.rb
new file mode 100644
index 0000000000..f2bbe0a856
--- /dev/null
+++ b/db/migrate/20190420025523_add_blurhash_to_media_attachments.rb
@@ -0,0 +1,5 @@
+class AddBlurhashToMediaAttachments < ActiveRecord::Migration[5.2]
+ def change
+ add_column :media_attachments, :blurhash, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5ffec228d9..26bbe30fe5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_04_09_054914) do
+ActiveRecord::Schema.define(version: 2019_04_20_025523) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -373,6 +373,7 @@ ActiveRecord::Schema.define(version: 2019_04_09_054914) do
t.bigint "account_id"
t.text "description"
t.bigint "scheduled_status_id"
+ t.string "blurhash"
t.index ["account_id"], name: "index_media_attachments_on_account_id"
t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id"
t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true
diff --git a/lib/cli.rb b/lib/cli.rb
index b56c6e76f5..5780e3e873 100644
--- a/lib/cli.rb
+++ b/lib/cli.rb
@@ -9,6 +9,7 @@ require_relative 'mastodon/search_cli'
require_relative 'mastodon/settings_cli'
require_relative 'mastodon/statuses_cli'
require_relative 'mastodon/domains_cli'
+require_relative 'mastodon/cache_cli'
require_relative 'mastodon/version'
module Mastodon
@@ -41,6 +42,9 @@ module Mastodon
desc 'domains SUBCOMMAND ...ARGS', 'Manage account domains'
subcommand 'domains', Mastodon::DomainsCLI
+ desc 'cache SUBCOMMAND ...ARGS', 'Manage cache'
+ subcommand 'cache', Mastodon::CacheCLI
+
option :dry_run, type: :boolean
desc 'self-destruct', 'Erase the server from the federation'
long_desc <<~LONG_DESC
diff --git a/lib/mastodon/accounts_cli.rb b/lib/mastodon/accounts_cli.rb
index 9dc84f1b52..3131647f35 100644
--- a/lib/mastodon/accounts_cli.rb
+++ b/lib/mastodon/accounts_cli.rb
@@ -73,7 +73,7 @@ module Mastodon
def create(username)
account = Account.new(username: username)
password = SecureRandom.hex
- user = User.new(email: options[:email], password: password, agreement: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil)
+ user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil)
if options[:reattach]
account = Account.find_local(username) || Account.new(username: username)
@@ -115,6 +115,7 @@ module Mastodon
option :enable, type: :boolean
option :disable, type: :boolean
option :disable_2fa, type: :boolean
+ option :approve, type: :boolean
desc 'modify USERNAME', 'Modify a user'
long_desc <<-LONG_DESC
Modify a user account.
@@ -128,6 +129,9 @@ module Mastodon
With the --disable option, lock the user out of their account. The
--enable option is the opposite.
+ With the --approve option, the account will be approved, if it was
+ previously not due to not having open registrations.
+
With the --disable-2fa option, the two-factor authentication
requirement for the user can be removed.
LONG_DESC
@@ -147,6 +151,7 @@ module Mastodon
user.email = options[:email] if options[:email]
user.disabled = false if options[:enable]
user.disabled = true if options[:disable]
+ user.approved = true if options[:approve]
user.otp_required_for_login = false if options[:disable_2fa]
user.confirm if options[:confirm]
diff --git a/lib/mastodon/cache_cli.rb b/lib/mastodon/cache_cli.rb
new file mode 100644
index 0000000000..e9b6667b3b
--- /dev/null
+++ b/lib/mastodon/cache_cli.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require_relative '../../config/boot'
+require_relative '../../config/environment'
+require_relative 'cli_helper'
+
+module Mastodon
+ class CacheCLI < Thor
+ def self.exit_on_failure?
+ true
+ end
+
+ desc 'clear', 'Clear out the cache storage'
+ def clear
+ Rails.cache.clear
+ say('OK', :green)
+ end
+ end
+end
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 8172aeb941..5ea569b296 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -13,7 +13,7 @@ module Mastodon
end
def patch
- 0
+ 1
end
def pre
@@ -46,7 +46,7 @@ module Mastodon
# specify git tag or commit hash here
def source_tag
- nil
+ ENV.fetch('SOURCE_TAG') { nil }
end
def source_url
diff --git a/lib/paperclip/blurhash_transcoder.rb b/lib/paperclip/blurhash_transcoder.rb
new file mode 100644
index 0000000000..08925a6dde
--- /dev/null
+++ b/lib/paperclip/blurhash_transcoder.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Paperclip
+ class BlurhashTranscoder < Paperclip::Processor
+ def make
+ return @file unless options[:style] == :small
+
+ pixels = convert(':source RGB:-', source: File.expand_path(@file.path)).unpack('C*')
+ geometry = options.fetch(:file_geometry_parser).from_file(@file)
+
+ attachment.instance.blurhash = Blurhash.encode(geometry.width, geometry.height, pixels, options[:blurhash] || {})
+
+ @file
+ end
+ end
+end
diff --git a/package.json b/package.json
index b5963acd44..8262c73aae 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
"babel-plugin-react-intl": "^3.0.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"babel-runtime": "^6.26.0",
+ "blurhash": "^1.0.0",
"classnames": "^2.2.5",
"compression-webpack-plugin": "^2.0.0",
"cross-env": "^5.1.4",
diff --git a/public/robots.txt b/public/robots.txt
index d93648beee..771bf2160b 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -2,3 +2,4 @@
User-agent: *
Disallow: /media_proxy/
+Disallow: /interact/
diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb
index 129bf88837..2a8675c21a 100644
--- a/spec/controllers/admin/domain_blocks_controller_spec.rb
+++ b/spec/controllers/admin/domain_blocks_controller_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
end
it 'renders new when failed to save' do
- Fabricate(:domain_block, domain: 'example.com')
+ Fabricate(:domain_block, domain: 'example.com', severity: 'suspend')
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
@@ -45,6 +45,17 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do
expect(DomainBlockWorker).not_to have_received(:perform_async)
expect(response).to render_template :new
end
+
+ it 'allows upgrading a block' do
+ Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
+ allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
+
+ post :create, params: { domain_block: { domain: 'example.com', severity: 'silence', reject_media: true, reject_reports: true } }
+
+ expect(DomainBlockWorker).to have_received(:perform_async)
+ expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
+ expect(response).to redirect_to(admin_instances_path(limited: '1'))
+ end
end
describe 'DELETE #destroy' do
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index 1095df034e..a4337039e1 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -107,6 +107,89 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
end
end
+ context 'approval-based registrations without invite' do
+ around do |example|
+ registrations_mode = Setting.registrations_mode
+ example.run
+ Setting.registrations_mode = registrations_mode
+ end
+
+ subject do
+ Setting.registrations_mode = 'approved'
+ request.headers["Accept-Language"] = accept_language
+ post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
+ end
+
+ it 'redirects to login page' do
+ subject
+ expect(response).to redirect_to new_user_session_path
+ end
+
+ it 'creates user' do
+ subject
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
+ expect(user.approved).to eq(false)
+ end
+ end
+
+ context 'approval-based registrations with expired invite' do
+ around do |example|
+ registrations_mode = Setting.registrations_mode
+ example.run
+ Setting.registrations_mode = registrations_mode
+ end
+
+ subject do
+ Setting.registrations_mode = 'approved'
+ request.headers["Accept-Language"] = accept_language
+ invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.ago)
+ post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
+ end
+
+ it 'redirects to login page' do
+ subject
+ expect(response).to redirect_to new_user_session_path
+ end
+
+ it 'creates user' do
+ subject
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
+ expect(user.approved).to eq(false)
+ end
+ end
+
+ context 'approval-based registrations with valid invite' do
+ around do |example|
+ registrations_mode = Setting.registrations_mode
+ example.run
+ Setting.registrations_mode = registrations_mode
+ end
+
+ subject do
+ Setting.registrations_mode = 'approved'
+ request.headers["Accept-Language"] = accept_language
+ invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.from_now)
+ post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code } }
+ end
+
+ it 'redirects to login page' do
+ subject
+ expect(response).to redirect_to new_user_session_path
+ end
+
+ it 'creates user' do
+ subject
+ user = User.find_by(email: 'test@example.com')
+ expect(user).to_not be_nil
+ expect(user.locale).to eq(accept_language)
+ expect(user.approved).to eq(true)
+ end
+ end
+
it 'does nothing if user already exists' do
Fabricate(:user, account: Fabricate(:account, username: 'test'))
subject
diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb
index 89cadccfec..0035fd0ffa 100644
--- a/spec/models/domain_block_spec.rb
+++ b/spec/models/domain_block_spec.rb
@@ -36,4 +36,35 @@ RSpec.describe DomainBlock, type: :model do
expect(DomainBlock.blocked?('domain')).to eq false
end
end
+
+ describe 'stricter_than?' do
+ it 'returns true if the new block has suspend severity while the old has lower severity' do
+ suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
+ silence = DomainBlock.new(domain: 'domain', severity: :silence)
+ noop = DomainBlock.new(domain: 'domain', severity: :noop)
+ expect(suspend.stricter_than?(silence)).to be true
+ expect(suspend.stricter_than?(noop)).to be true
+ end
+
+ it 'returns false if the new block has lower severity than the old one' do
+ suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
+ silence = DomainBlock.new(domain: 'domain', severity: :silence)
+ noop = DomainBlock.new(domain: 'domain', severity: :noop)
+ expect(silence.stricter_than?(suspend)).to be false
+ expect(noop.stricter_than?(suspend)).to be false
+ expect(noop.stricter_than?(silence)).to be false
+ end
+
+ it 'returns false if the new block does is less strict regarding reports' do
+ older = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: true)
+ newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: false)
+ expect(newer.stricter_than?(older)).to be false
+ end
+
+ it 'returns false if the new block does is less strict regarding media' do
+ older = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: true)
+ newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: false)
+ expect(newer.stricter_than?(older)).to be false
+ end
+ end
end
diff --git a/spec/validators/blacklisted_email_validator_spec.rb b/spec/validators/blacklisted_email_validator_spec.rb
index d2e442f4ab..84b0107dd7 100644
--- a/spec/validators/blacklisted_email_validator_spec.rb
+++ b/spec/validators/blacklisted_email_validator_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
let(:errors) { double(add: nil) }
before do
+ allow(user).to receive(:invited?) { false }
allow_any_instance_of(described_class).to receive(:blocked_email?) { blocked_email }
described_class.new.validate(user)
end
diff --git a/yarn.lock b/yarn.lock
index 11fe49fa6e..377a3523d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1747,6 +1747,11 @@ bluebird@^3.5.1, bluebird@^3.5.3:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+blurhash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.0.0.tgz#9087bc5cc4d482f1305059d7410df4133adcab2e"
+ integrity sha512-x6fpZnd6AWde4U9m7xhUB44qIvGV4W6OdTAXGabYm4oZUOOGh5K1HAEoGAQn3iG4gbbPn9RSGce3VfNgGsX/Vw==
+
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"