Merge branch 'master' into fix/cache_blocking

This commit is contained in:
Effy Elden 2017-04-17 01:41:33 +10:00 committed by GitHub
commit acd33101c5
48 changed files with 453 additions and 184 deletions

View file

@ -14,6 +14,7 @@ addons:
postgresql: 9.4 postgresql: 9.4
rvm: rvm:
- 2.3.4
- 2.4.1 - 2.4.1
services: services:

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '2.4.1' ruby '>= 2.3.0', '< 2.5.0'
gem 'pkg-config' gem 'pkg-config'
@ -88,7 +88,7 @@ group :development do
gem 'bullet' gem 'bullet'
gem 'active_record_query_trace' gem 'active_record_query_trace'
gem 'capistrano' gem 'capistrano', '3.8.0'
gem 'capistrano-rails' gem 'capistrano-rails'
gem 'capistrano-rbenv' gem 'capistrano-rbenv'
gem 'capistrano-yarn' gem 'capistrano-yarn'

View file

@ -41,7 +41,7 @@ GEM
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.5.1) addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2) public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.1.2) airbrussh (1.2.0)
sshkit (>= 1.6.1, != 1.7.0) sshkit (>= 1.6.1, != 1.7.0)
arel (7.1.4) arel (7.1.4)
ast (2.3.0) ast (2.3.0)
@ -469,7 +469,7 @@ DEPENDENCIES
binding_of_caller binding_of_caller
browserify-rails browserify-rails
bullet bullet
capistrano capistrano (= 3.8.0)
capistrano-faster-assets (~> 1.0) capistrano-faster-assets (~> 1.0)
capistrano-rails capistrano-rails
capistrano-rbenv capistrano-rbenv

View file

@ -3,3 +3,4 @@
* * * * * * * *
- [ ] I searched or browsed the repos other issues to ensure this is not a duplicate. - [ ] I searched or browsed the repos other issues to ensure this is not a duplicate.
- [ ] This bug happens on a [tagged release](https://github.com/tootsuite/mastodon/releases) and not on `master` (If you're a user, don't worry about this).

View file

@ -48,6 +48,14 @@ If you would like, you can [support the development of this project on Patreon][
- **Deployable via Docker** - **Deployable via Docker**
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
## Checking out
If you want a stable release for production use, you should use tagged releases. To checkout the latest available tagged version:
git clone https://github.com/tootsuite/mastodon.git
cd mastodon
git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
## Configuration ## Configuration
- `LOCAL_DOMAIN` should be the domain/hostname of your instance. This is **absolutely required** as it is used for generating unique IDs for everything federation-related - `LOCAL_DOMAIN` should be the domain/hostname of your instance. This is **absolutely required** as it is used for generating unique IDs for everything federation-related

View file

@ -24,8 +24,10 @@ const makeGetStatusIds = () => createSelector([
if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) { if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) {
try { try {
const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i'); if (showStatus) {
showStatus = showStatus && !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'content']) : statusForId.get('content')); const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i');
showStatus = !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'unescaped_content']) : statusForId.get('unescaped_content'));
}
} catch(e) { } catch(e) {
// Bad regex, don't affect filters // Bad regex, don't affect filters
} }

View file

@ -75,6 +75,7 @@ const fr = {
"navigation_bar.favourites": "Favoris", "navigation_bar.favourites": "Favoris",
"navigation_bar.info": "Plus d'informations", "navigation_bar.info": "Plus d'informations",
"navigation_bar.logout": "Déconnexion", "navigation_bar.logout": "Déconnexion",
"navigation_bar.mutes": "Utilisateurs muets",
"navigation_bar.follow_requests": "Demandes de suivi", "navigation_bar.follow_requests": "Demandes de suivi",
"reply_indicator.cancel": "Annuler", "reply_indicator.cancel": "Annuler",
"search.placeholder": "Rechercher", "search.placeholder": "Rechercher",

View file

@ -1,121 +1,125 @@
const ja = { const ja = {
"column_back_button.label": "戻る", "account.block": "@{name} さんをブロック",
"lightbox.close": "閉じる", "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
"loading_indicator.label": "読み込み中...",
"status.mention": "@{name} さんへの返信",
"status.delete": "削除",
"status.reply": "返信",
"status.reblog": "ブースト",
"status.favourite": "お気に入り",
"status.reblogged_by": "{name} さんにブーストされました",
"status.sensitive_warning": "不適切なコンテンツ",
"status.sensitive_toggle": "クリックして表示",
"status.show_more": "もっと見る",
"status.load_more": "もっと見る",
"status.show_less": "隠す",
"status.open": "Expand this status",
"status.report": "@{name} さんを通報",
"status.media_hidden": "非表示のメデイア",
"video_player.toggle_sound": "音の切り替え",
"account.mention": "@{name} さんに返信",
"account.edit_profile": "プロフィールを編集", "account.edit_profile": "プロフィールを編集",
"account.follow": "フォロー",
"account.followers": "フォロワー",
"account.follows": "フォロー",
"account.follows_you": "フォローされています",
"account.mention": "@{name} さんに返信",
"account.mute": "ミュート",
"account.posts": "投稿",
"account.report": "@{name}を通報する",
"account.requested": "承認待ち",
"account.unblock": "@{name} さんのブロックを解除", "account.unblock": "@{name} さんのブロックを解除",
"account.unfollow": "フォロー解除", "account.unfollow": "フォロー解除",
"account.block": "@{name} さんをブロック",
"account.mute": "ミュート",
"account.unmute": "ミュート解除", "account.unmute": "ミュート解除",
"account.follow": "フォロー", "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
"account.report": "@{name}を通報する", "column.blocks": "ブロックしたユーザー",
"account.posts": "投稿",
"account.follows": "フォロー",
"account.followers": "フォロワー",
"account.follows_you": "フォローされています",
"account.requested": "承認待ち",
"follow_request.authorize": "許可",
"follow_request.reject": "拒否",
"getting_started.heading": "スタート",
"getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。",
"getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。",
"getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub{github})から開発に参加したり、問題を報告したりできます。 {apps}",
"getting_started.apps": "さまざまなアプリで利用できます。",
"column.home": "ホーム",
"column.community": "ローカルタイムライン", "column.community": "ローカルタイムライン",
"column.public": "連合タイムライン",
"column.notifications": "通知",
"column.favourites": "お気に入り", "column.favourites": "お気に入り",
"tabs_bar.compose": "投稿", "column.follow_requests": "フォローリクエスト",
"tabs_bar.home": "ホーム", "column.home": "ホーム",
"tabs_bar.mentions": "返信", "column.mutes": "ミュートしたユーザー",
"tabs_bar.local_timeline": "ローカル", "column.notifications": "通知",
"tabs_bar.federated_timeline": "連合", "column.public": "連合タイムライン",
"tabs_bar.notifications": "通知", "column_back_button.label": "戻る",
"compose_form.placeholder": "今なにしてる?", "compose_form.placeholder": "今なにしてる?",
"compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザーat {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。",
"compose_form.publish": "トゥート", "compose_form.publish": "トゥート",
"compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする", "compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする",
"compose_form.spoiler": "テキストを隠す", "compose_form.spoiler": "テキストを隠す",
"compose_form.spoiler_placeholder": "内容注意メッセージ", "compose_form.spoiler_placeholder": "閲覧注意",
"compose_form.private": "非公開にする", "emoji_button.label": "絵文字を追加",
"compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザーat {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
"compose_form.unlisted": "公開タイムラインに表示しない", "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
"privacy.public.short": "公開",
"privacy.public.long": "公開TLに投稿する",
"privacy.unlisted.short": "未収載",
"privacy.unlisted.long": "公開TLで表示しない",
"privacy.private.short": "非公開",
"privacy.private.long": "フォロワーだけに公開",
"privacy.direct.short": "ダイレクト",
"privacy.direct.long": "含んだユーザーだけに公開",
"privacy.change": "投稿のプライバシーを変更",
"report.heading": "新規通報",
"report.placeholder": "コメント",
"report.target": "問題のユーザー",
"report.submit": "通報する",
"navigation_bar.edit_profile": "プロフィールを編集",
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.community_timeline": "ローカルタイムライン",
"navigation_bar.public_timeline": "連合タイムライン",
"navigation_bar.logout": "ログアウト",
"navigation_bar.favourites": "お気に入り",
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.info": "サーバー情報",
"reply_indicator.cancel": "キャンセル",
"search.placeholder": "検索",
"search.account": "アカウント",
"search.hashtag": "ハッシュタグ",
"search.status_by": "{uuuname}からの投稿",
"search_results.total": "{count} 件",
"upload_area.title": "ファイルをこちらにドラッグしてください",
"upload_button.label": "メディアを追加",
"upload_form.undo": "やり直す",
"notification.follow": "{name} さんにフォローされました",
"notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
"notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
"notification.mention": "{name} さんがあなたに返信しました",
"notifications.clear": "通知を片付ける",
"notifications.clear_confirmation": "通知を全部片付けます。大丈夫ですか?",
"notifications.column_settings.alert": "デスクトップ通知",
"notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.follow": "新しいフォロワー",
"notifications.column_settings.favourite": "お気に入り",
"notifications.column_settings.mention": "返信",
"notifications.column_settings.reblog": "ブースト",
"notifications.column_settings.sound": "通知音を再生",
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
"empty_column.home.public_timeline": "連合タイムライン", "empty_column.home.public_timeline": "連合タイムライン",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!", "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
"empty_column.hashtag": "このハッシュタグはまだ使っていません。", "follow_request.authorize": "許可",
"upload_progress.label": "アップロード中…", "follow_request.reject": "拒否",
"emoji_button.label": "絵文字を追加", "getting_started.apps": "さまざまなアプリで利用できます。",
"getting_started.heading": "スタート",
"getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub{github})から開発に参加したり、問題を報告したりできます。 {apps}",
"home.column_settings.advanced": "上級者向け",
"home.column_settings.basic": "シンプル", "home.column_settings.basic": "シンプル",
"home.column_settings.advanced": "エキスパート", "home.column_settings.filter_regex": "正規表現でフィルター",
"home.column_settings.show_reblogs": "ブースト表示", "home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示", "home.column_settings.show_replies": "返信表示",
"home.column_settings.filter_regex": "正規表現でフィルター",
"home.settings": "カラム設定", "home.settings": "カラム設定",
"notifications.settings": "カラム設定", "lightbox.close": "閉じる",
"loading_indicator.label": "読み込み中...",
"media_gallery.toggle_visible": "表示切り替え",
"missing_indicator.label": "見つかりません", "missing_indicator.label": "見つかりません",
"boost_modal.combo": "次は{combo}を押せば、これをスキップできます。" "navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.community_timeline": "ローカルタイムライン",
"navigation_bar.edit_profile": "プロフィールを編集",
"navigation_bar.favourites": "お気に入り",
"navigation_bar.follow_requests": "フォローリクエスト",
"navigation_bar.info": "サーバー情報",
"navigation_bar.logout": "ログアウト",
"navigation_bar.mutes": "ミュートしたユーザー",
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.public_timeline": "連合タイムライン",
"notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
"notification.follow": "{name} さんにフォローされました",
"notification.mention": "{name} さんがあなたに返信しました",
"notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
"notifications.clear": "通知を消去",
"notifications.clear_confirmation": "本当に通知を消去しますか?",
"notifications.column_settings.alert": "デスクトップ通知",
"notifications.column_settings.favourite": "お気に入り",
"notifications.column_settings.follow": "新しいフォロワー",
"notifications.column_settings.mention": "返信",
"notifications.column_settings.reblog": "ブースト",
"notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.sound": "通知音を再生",
"notifications.settings": "カラム設定",
"privacy.change": "投稿のプライバシーを変更",
"privacy.direct.long": "メンションしたユーザーだけに公開",
"privacy.direct.short": "ダイレクト",
"privacy.private.long": "フォロワーだけに公開",
"privacy.private.short": "非公開",
"privacy.public.long": "公開TLに投稿する",
"privacy.public.short": "公開",
"privacy.unlisted.long": "公開TLで表示しない",
"privacy.unlisted.short": "未収載",
"reply_indicator.cancel": "キャンセル",
"report.heading": "新規通報",
"report.placeholder": "コメント",
"report.submit": "通報する",
"report.target": "問題のユーザー",
"search.placeholder": "検索",
"search.status_by": "{name}からの投稿",
"search_results.total": "{count} {count, plural, one {result} other {results}} 件",
"status.delete": "削除",
"status.favourite": "お気に入り",
"status.load_more": "もっと見る",
"status.media_hidden": "非表示のメデイア",
"status.mention": "@{name} さんへの返信",
"status.open": "詳細を表示",
"status.reblog": "ブースト",
"status.reblogged_by": "{name} さんにブーストされました",
"status.reply": "返信",
"status.report": "@{name} さんを通報",
"status.sensitive_toggle": "クリックして表示",
"status.sensitive_warning": "不適切なコンテンツ",
"status.show_less": "隠す",
"status.show_more": "もっと見る",
"tabs_bar.compose": "投稿",
"tabs_bar.federated_timeline": "連合",
"tabs_bar.home": "ホーム",
"tabs_bar.local_timeline": "ローカル",
"tabs_bar.notifications": "通知",
"upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加",
"upload_form.undo": "やり直す",
"upload_progress.label": "アップロード中…",
"video_player.expand": "動画の詳細",
"video_player.toggle_sound": "音の切り替え",
"video_player.toggle_visible": "表示切り替え",
"video_player.video_error": "動画の再生に失敗しました",
}; };
export default ja; export default ja;

View file

@ -48,6 +48,9 @@ const normalizeStatus = (state, status) => {
normalStatus.reblog = status.reblog.id; normalStatus.reblog = status.reblog.id;
} }
const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent;
return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus))); return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
}; };

View file

@ -1203,6 +1203,10 @@ a.status__content__spoiler-link {
&:focus { &:focus {
outline: 0; outline: 0;
} }
@media screen and (max-width: 600px) {
font-size: 16px;
}
} }
.spoiler-input__input { .spoiler-input__input {
@ -1267,6 +1271,10 @@ a.status__content__spoiler-link {
color: $color5; color: $color5;
border-bottom-color: $color4; border-bottom-color: $color4;
} }
@media screen and (max-width: 600px) {
font-size: 16px;
}
} }
@import 'boost'; @import 'boost';
@ -1906,6 +1914,10 @@ button.icon-button.active i.fa-retweet {
&:focus { &:focus {
background: lighten($color1, 4%); background: lighten($color1, 4%);
} }
@media screen and (max-width: 600px) {
font-size: 16px;
}
} }
.search__icon { .search__icon {

View file

@ -15,16 +15,26 @@ module Admin
if @domain_block.save if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id) DomainBlockWorker.perform_async(@domain_block.id)
redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed' redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_block.created_msg')
else else
render action: :new render action: :new
end end
end end
def show
@domain_block = DomainBlock.find(params[:id])
end
def destroy
@domain_block = DomainBlock.find(params[:id])
UnblockDomainService.new.call(@domain_block, resource_params[:retroactive])
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_block.destroyed_msg')
end
private private
def resource_params def resource_params
params.require(:domain_block).permit(:domain, :severity) params.require(:domain_block).permit(:domain, :severity, :reject_media, :retroactive)
end end
end end
end end

View file

@ -8,7 +8,9 @@ class ApplicationController < ActionController::Base
force_ssl if: "Rails.env.production? && ENV['LOCAL_HTTPS'] == 'true'" force_ssl if: "Rails.env.production? && ENV['LOCAL_HTTPS'] == 'true'"
include Localized include Localized
helper_method :current_account, :single_user_mode?
helper_method :current_account
helper_method :single_user_mode?
rescue_from ActionController::RoutingError, with: :not_found rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found

View file

@ -3,6 +3,8 @@
class DomainBlock < ApplicationRecord class DomainBlock < ApplicationRecord
enum severity: [:silence, :suspend] enum severity: [:silence, :suspend]
attr_accessor :retroactive
validates :domain, presence: true, uniqueness: true validates :domain, presence: true, uniqueness: true
def self.blocked?(domain) def self.blocked?(domain)

View file

@ -1,13 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class Import < ApplicationRecord class Import < ApplicationRecord
FILE_TYPES = ['text/plain', 'text/csv'].freeze
self.inheritance_column = false self.inheritance_column = false
belongs_to :account, required: true
enum type: [:following, :blocking, :muting] enum type: [:following, :blocking, :muting]
belongs_to :account validates :type, presence: true
FILE_TYPES = ['text/plain', 'text/csv'].freeze
has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET'] has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET']
validates_attachment_content_type :data, content_type: FILE_TYPES validates_attachment_content_type :data, content_type: FILE_TYPES

View file

@ -110,6 +110,10 @@ class Status < ApplicationRecord
results results
end end
def non_sensitive_with_media?
!sensitive? && media_attachments.any?
end
class << self class << self
def as_home_timeline(account) def as_home_timeline(account)
where(account: [account] + account.following) where(account: [account] + account.following)

View file

@ -3,12 +3,34 @@
class BlockDomainService < BaseService class BlockDomainService < BaseService
def call(domain_block) def call(domain_block)
if domain_block.silence? if domain_block.silence?
Account.where(domain: domain_block.domain).update_all(silenced: true) silence_accounts!(domain_block.domain)
clear_media!(domain_block.domain) if domain_block.reject_media?
else else
Account.where(domain: domain_block.domain).find_each do |account| suspend_accounts!(domain_block.domain)
account.subscription(api_subscription_url(account.id)).unsubscribe if account.subscribed? end
SuspendAccountService.new.call(account) end
end
private
def silence_accounts!(domain)
Account.where(domain: domain).update_all(silenced: true)
end
def clear_media!(domain)
Account.where(domain: domain).find_each do |account|
account.avatar.destroy
account.header.destroy
end
MediaAttachment.where(account: Account.where(domain: domain)).find_each do |attachment|
attachment.file.destroy
end
end
def suspend_accounts!(domain)
Account.where(domain: domain).where(suspended: false).find_each do |account|
account.subscription(api_subscription_url(account.id)).unsubscribe if account.subscribed?
SuspendAccountService.new.call(account)
end end
end end
end end

View file

@ -16,7 +16,7 @@ class FollowRemoteAccountService < BaseService
return Account.find_local(username) if TagManager.instance.local_domain?(domain) return Account.find_local(username) if TagManager.instance.local_domain?(domain)
account = Account.find_remote(username, domain) account = Account.find_remote(username, domain)
return account unless account&.last_webfingered_at.nil? || 1.day.from_now(account.last_webfingered_at) < Time.now.utc return account unless account_needs_webfinger_update?(account)
Rails.logger.debug "Looking up webfinger for #{uri}" Rails.logger.debug "Looking up webfinger for #{uri}"
@ -62,6 +62,10 @@ class FollowRemoteAccountService < BaseService
private private
def account_needs_webfinger_update?(account)
account&.last_webfingered_at.nil? || account.last_webfingered_at <= 1.day.ago
end
def get_feed(url) def get_feed(url)
response = http_client.get(Addressable::URI.parse(url)) response = http_client.get(Addressable::URI.parse(url))
[response.to_s, Nokogiri::XML(response)] [response.to_s, Nokogiri::XML(response)]

View file

@ -179,12 +179,12 @@ class ProcessFeedService < BaseService
end end
def hashtags_from_xml(parent, xml) def hashtags_from_xml(parent, xml)
tags = xml.xpath('./xmlns:category', xmlns: TagManager::XMLNS).map { |category| category['term'] }.select { |t| !t.blank? } tags = xml.xpath('./xmlns:category', xmlns: TagManager::XMLNS).map { |category| category['term'] }.select(&:present?)
ProcessHashtagsService.new.call(parent, tags) ProcessHashtagsService.new.call(parent, tags)
end end
def media_from_xml(parent, xml) def media_from_xml(parent, xml)
return if DomainBlock.find_by(domain: parent.account.domain)&.reject_media? do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: TagManager::XMLNS).each do |link| xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: TagManager::XMLNS).each do |link|
next unless link['href'] next unless link['href']
@ -192,7 +192,11 @@ class ProcessFeedService < BaseService
media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href']) media = MediaAttachment.where(status: parent, remote_url: link['href']).first_or_initialize(account: parent.account, status: parent, remote_url: link['href'])
parsed_url = URI.parse(link['href']) parsed_url = URI.parse(link['href'])
next if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.empty? next if !%w[http https].include?(parsed_url.scheme) || parsed_url.host.empty?
media.save
next if do_not_download
begin begin
media.file_remote_url = link['href'] media.file_remote_url = link['href']

View file

@ -13,6 +13,7 @@ class SuspendAccountService < BaseService
def purge_content def purge_content
@account.statuses.reorder(nil).find_each do |status| @account.statuses.reorder(nil).find_each do |status|
# This federates out deletes to previous followers
RemoveStatusService.new.call(status) RemoveStatusService.new.call(status)
end end
@ -29,9 +30,7 @@ class SuspendAccountService < BaseService
@account.display_name = '' @account.display_name = ''
@account.note = '' @account.note = ''
@account.avatar.destroy @account.avatar.destroy
@account.avatar.clear
@account.header.destroy @account.header.destroy
@account.header.clear
@account.save! @account.save!
end end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class UnblockDomainService < BaseService
def call(domain_block, retroactive)
if retroactive
if domain_block.silence?
Account.where(domain: domain_block.domain).update_all(silenced: false)
else
Account.where(domain: domain_block.domain).update_all(suspended: false)
end
end
domain_block.destroy
end
end

View file

@ -6,6 +6,7 @@ class UnfollowService < BaseService
# @param [Account] target_account Which to unfollow # @param [Account] target_account Which to unfollow
def call(source_account, target_account) def call(source_account, target_account)
follow = source_account.unfollow!(target_account) follow = source_account.unfollow!(target_account)
return unless follow
NotificationWorker.perform_async(build_xml(follow), source_account.id, target_account.id) unless target_account.local? NotificationWorker.perform_async(build_xml(follow), source_account.id, target_account.id) unless target_account.local?
UnmergeWorker.perform_async(target_account.id, source_account.id) UnmergeWorker.perform_async(target_account.id, source_account.id)
end end

View file

@ -1,34 +1,34 @@
.card.h-card.p-author{ style: "background-image: url(#{@account.header.url( :original)})" } .card.h-card.p-author{ style: "background-image: url(#{account.header.url( :original)})" }
- if user_signed_in? && current_account.id != @account.id && !current_account.requested?(@account) - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
.controls .controls
- if current_account.following?(@account) - if current_account.following?(account)
= link_to t('accounts.unfollow'), unfollow_account_path(@account), data: { method: :post }, class: 'button' = link_to t('accounts.unfollow'), unfollow_account_path(account), data: { method: :post }, class: 'button'
- else - else
= link_to t('accounts.follow'), follow_account_path(@account), data: { method: :post }, class: 'button' = link_to t('accounts.follow'), follow_account_path(account), data: { method: :post }, class: 'button'
- elsif !user_signed_in? - elsif !user_signed_in?
.controls .controls
.remote-follow .remote-follow
= link_to t('accounts.remote_follow'), account_remote_follow_path(@account), class: 'button' = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button'
.avatar= image_tag @account.avatar.url(:original), class: 'u-photo' .avatar= image_tag account.avatar.url(:original), class: 'u-photo'
%h1.name %h1.name
%span.p-name.emojify= display_name(@account) %span.p-name.emojify= display_name(account)
%small %small
%span= "@#{@account.username}" %span= "@#{account.username}"
= fa_icon('lock') if @account.locked? = fa_icon('lock') if account.locked?
.details .details
.bio .bio
.account__header__content.p-note.emojify= Formatter.instance.simplified_format(@account) .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account)
.details-counters .details-counters
.counter{ class: active_nav_class(short_account_url(@account)) } .counter{ class: active_nav_class(short_account_url(account)) }
= link_to short_account_url(@account), class: 'u-url u-uid' do = link_to short_account_url(account), class: 'u-url u-uid' do
%span.counter-label= t('accounts.posts') %span.counter-label= t('accounts.posts')
%span.counter-number= number_with_delimiter @account.statuses_count %span.counter-number= number_with_delimiter account.statuses_count
.counter{ class: active_nav_class(following_account_url(@account)) } .counter{ class: active_nav_class(following_account_url(account)) }
= link_to following_account_url(@account) do = link_to following_account_url(account) do
%span.counter-label= t('accounts.following') %span.counter-label= t('accounts.following')
%span.counter-number= number_with_delimiter @account.following_count %span.counter-number= number_with_delimiter account.following_count
.counter{ class: active_nav_class(followers_account_url(@account)) } .counter{ class: active_nav_class(followers_account_url(account)) }
= link_to followers_account_url(@account) do = link_to followers_account_url(account) do
%span.counter-label= t('accounts.followers') %span.counter-label= t('accounts.followers')
%span.counter-number= number_with_delimiter @account.followers_count %span.counter-number= number_with_delimiter account.followers_count

View file

@ -1,7 +1,7 @@
- content_for :page_title do - content_for :page_title do
= t('accounts.people_who_follow', name: display_name(@account)) = t('accounts.people_who_follow', name: display_name(@account))
= render partial: 'header' = render 'header', account: @account
.accounts-grid .accounts-grid
- if @followers.empty? - if @followers.empty?

View file

@ -1,7 +1,7 @@
- content_for :page_title do - content_for :page_title do
= t('accounts.people_followed_by', name: display_name(@account)) = t('accounts.people_followed_by', name: display_name(@account))
= render partial: 'header' = render 'header', account: @account
.accounts-grid .accounts-grid
- if @following.empty? - if @following.empty?

View file

@ -20,7 +20,7 @@
.h-feed .h-feed
%data.p-name{ value: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/ %data.p-name{ value: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
= render partial: 'header' = render 'header', account: @account
- if @statuses.empty? - if @statuses.empty?
.accounts-grid .accounts-grid

View file

@ -6,12 +6,19 @@
%tr %tr
%th= t('admin.domain_block.domain') %th= t('admin.domain_block.domain')
%th= t('admin.domain_block.severity') %th= t('admin.domain_block.severity')
%th= t('admin.domain_block.reject_media')
%th
%tbody %tbody
- @blocks.each do |block| - @blocks.each do |block|
%tr %tr
%td %td
%samp= block.domain %samp= block.domain
%td= block.severity %td= t("admin.domain_block.severities.#{block.severity}")
%td
- if block.reject_media? || block.suspend?
%i.fa.fa-check
%td
= table_link_to 'undo', t('admin.domain_block.undo'), admin_domain_block_path(block)
= paginate @blocks = paginate @blocks
= link_to t('admin.domain_block.add_new'), new_admin_domain_block_path, class: 'button' = link_to t('admin.domain_block.add_new'), new_admin_domain_block_path, class: 'button'

View file

@ -10,5 +10,8 @@
= f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| I18n.t("admin.domain_block.new.severity.#{type}") } = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| I18n.t("admin.domain_block.new.severity.#{type}") }
%p.hint= t('admin.domain_block.new.severity.desc_html') %p.hint= t('admin.domain_block.new.severity.desc_html')
= f.input :reject_media, as: :boolean, wrapper: :with_label, label: I18n.t('admin.domain_block.reject_media'), hint: I18n.t('admin.domain_block.reject_media_hint')
.actions .actions
= f.button :button, t('admin.domain_block.new.create'), type: :submit = f.button :button, t('admin.domain_block.new.create'), type: :submit

View file

@ -0,0 +1,9 @@
- content_for :page_title do
= t('admin.domain_block.show.title', domain: @domain_block.domain)
= simple_form_for @domain_block, url: admin_domain_block_path(@domain_block), method: :delete do |f|
= f.input :retroactive, as: :boolean, wrapper: :with_label, label: I18n.t("admin.domain_block.show.retroactive.#{@domain_block.severity}"), hint: I18n.t('admin.domain_block.show.affected_accounts', count: Account.where(domain: @domain_block.domain).count)
.actions
= f.button :button, t('admin.domain_block.show.undo'), type: :submit

View file

@ -1,5 +1,5 @@
attributes :id, :remote_url, :type attributes :id, :remote_url, :type
node(:url) { |media| full_asset_url(media.file.url(:original)) } node(:url) { |media| media.file.blank? ? media.remote_url : full_asset_url(media.file.url(:original)) }
node(:preview_url) { |media| full_asset_url(media.file.url(:small)) } node(:preview_url) { |media| media.file.blank? ? media.remote_url : full_asset_url(media.file.url(:small)) }
node(:text_url) { |media| media.local? ? medium_url(media) : nil } node(:text_url) { |media| media.local? ? medium_url(media) : nil }

View file

@ -1,6 +1,6 @@
%p.hint= t('two_factor_auth.recovery_instructions') %p.hint= t('two_factor_auth.recovery_instructions')
%ol.recovery-codes %ol.recovery-codes
- @codes.each do |code| - recovery_codes.each do |code|
%li %li
%samp= code %samp= code

View file

@ -1,4 +1,4 @@
- content_for :page_title do - content_for :page_title do
= t('settings.two_factor_auth') = t('settings.two_factor_auth')
= render 'recovery_codes' = render partial: 'recovery_codes', object: @codes

View file

@ -1,4 +1,4 @@
- content_for :page_title do - content_for :page_title do
= t('settings.two_factor_auth') = t('settings.two_factor_auth')
= render 'recovery_codes' = render partial: 'recovery_codes', object: @codes

View file

@ -0,0 +1,4 @@
- if activity.is_a?(Status) && activity.spoiler_text?
%meta{ property: 'og:description', content: activity.spoiler_text }/
- else
%meta{ property: 'og:description', content: activity.content }/

View file

@ -0,0 +1,6 @@
- if activity.is_a?(Status) && activity.non_sensitive_with_media?
%meta{ property: 'og:image', content: full_asset_url(activity.media_attachments.first.file.url(:small)) }/
- else
%meta{ property: 'og:image', content: full_asset_url(account.avatar.url(:original)) }/
%meta{ property: 'og:image:width', content: '120' }/
%meta{ property: 'og:image:height', content: '120' }/

View file

@ -6,17 +6,8 @@
%meta{ property: 'og:type', content: 'article' }/ %meta{ property: 'og:type', content: 'article' }/
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/ %meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
- if @stream_entry.activity.is_a?(Status) && !@stream_entry.activity.spoiler_text.blank? = render 'stream_entries/og_description', activity: @stream_entry.activity
%meta{ property: 'og:description', content: @stream_entry.activity.spoiler_text }/ = render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account
- else
%meta{ property: 'og:description', content: @stream_entry.activity.content }/
- if @stream_entry.activity.is_a?(Status) && !@stream_entry.activity.sensitive? && @stream_entry.activity.media_attachments.size > 0
%meta{ property: 'og:image', content: full_asset_url(@stream_entry.activity.media_attachments.first.file.url(:small)) }/
- else
%meta{ property: 'og:image', content: full_asset_url(@account.avatar.url(:original)) }/
%meta{ property: 'og:image:width', content: '120' }/
%meta{ property: 'og:image:height', content: '120' }/
%meta{ property: 'twitter:card', content: 'summary' }/ %meta{ property: 'twitter:card', content: 'summary' }/

17
bin/rspec Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rspec' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
require "rubygems"
require "bundler/setup"
load Gem.bin_path("rspec-core", "rspec")

View file

@ -1,4 +1,7 @@
lock '3.7.2' # frozen_string_literal: true
lock '3.8.0'
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git') set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
set :branch, ENV.fetch('BRANCH', 'master') set :branch, ENV.fetch('BRANCH', 'master')

View file

@ -81,6 +81,8 @@ en:
web: Web web: Web
domain_block: domain_block:
add_new: Add new add_new: Add new
created_msg: Domain block is now being processed
destroyed_msg: Domain block has been undone
domain: Domain domain: Domain
new: new:
create: Create block create: Create block
@ -90,8 +92,22 @@ en:
silence: Silence silence: Silence
suspend: Suspend suspend: Suspend
title: New domain block title: New domain block
reject_media: Reject media files
reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
severities:
silence: Silence
suspend: Suspend
severity: Severity severity: Severity
show:
affected_accounts:
one: One account in the database affected
other: "%{count} accounts in the database affected"
retroactive:
silence: Unsilence all existing accounts from this domain
suspend: Unsuspend all existing accounts from this domain
title: Undo domain block for %{domain}
title: Domain Blocks title: Domain Blocks
undo: Undo
pubsubhubbub: pubsubhubbub:
callback_url: Callback URL callback_url: Callback URL
confirmed: Confirmed confirmed: Confirmed

View file

@ -71,6 +71,7 @@ ja:
profile_url: プロフィールURL profile_url: プロフィールURL
public: パブリック public: パブリック
push_subscription_expires: PuSH購読期限切れ push_subscription_expires: PuSH購読期限切れ
reset_password: パスワード再設定
salmon_url: Salmon URL salmon_url: Salmon URL
silence: サイレンス silence: サイレンス
statuses: トゥート数 statuses: トゥート数
@ -81,6 +82,8 @@ ja:
web: Web web: Web
domain_block: domain_block:
add_new: 新規追加 add_new: 新規追加
created_msg: ドメインブロック処理を完了しました
destroyed_msg: ドメインブロックを外しました
domain: ドメイン domain: ドメイン
new: new:
create: ブロックを作成 create: ブロックを作成
@ -90,8 +93,21 @@ ja:
silence: サイレンス silence: サイレンス
suspend: 停止 suspend: 停止
title: 新規ドメインブロック title: 新規ドメインブロック
reject_media: メディアファイルを拒否
reject_media_hint: ローカルに保村されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。
severities:
silence: サイレンス
suspend: 停止
severity: 深刻度 severity: 深刻度
show:
affected_accounts: "データベース中の%{count}個のアカウントに影響します"
retroactive:
silence: このドメインからの存在するすべてのアカウントのサイレンスを戻す
suspend: このドメインからの存在するすべてのアカウントの停止を戻す
title: "%{domain}のドメインブロックを戻す"
undo: 元に戻す
title: ドメインブロック title: ドメインブロック
undo: 元に戻す
pubsubhubbub: pubsubhubbub:
callback_url: コールバックURL callback_url: コールバックURL
confirmed: 確認済み confirmed: 確認済み
@ -106,7 +122,7 @@ ja:
delete: 削除 delete: 削除
id: ID id: ID
mark_as_resolved: 解決済みとしてマーク mark_as_resolved: 解決済みとしてマーク
report: 'レポート#%{id}' report: レポート#%{id}
reported_account: 報告対象アカウント reported_account: 報告対象アカウント
reported_by: 報告者 reported_by: 報告者
resolved: 解決済み resolved: 解決済み
@ -290,8 +306,13 @@ ja:
disable: 無効 disable: 無効
enable: 有効 enable: 有効
enabled_success: 二段階認証が有効になりました enabled_success: 二段階認証が有効になりました
generate_recovery_codes: 復元コードを生成
instructions_html: "<strong>Google Authenticatorか、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。</strong>これ以降、ログインするときはそのアプリで生成されるコードが必要になります。" instructions_html: "<strong>Google Authenticatorか、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。</strong>これ以降、ログインするときはそのアプリで生成されるコードが必要になります。"
lost_recovery_codes: リカバリコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリコードは無効になります。
manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:' manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
recovery_codes: リカバリーコード
recovery_codes_regenerated: リカバリーコードが再生成されました。
recovery_instructions: 携帯電話を紛失した場合、以下の内どれかのリカバリコードを使用してアカウントへアクセスすることができます。 リカバリコードは印刷して安全に保管してください。
setup: 初期設定 setup: 初期設定
warning: 現在認証アプリを設定できない場合、無効に設定して、有効にしないでください。 warning: 現在認証アプリを設定できない場合、無効に設定して、有効にしないでください。
wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。 wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。

View file

@ -2,28 +2,97 @@
pt: pt:
about: about:
about_mastodon: Mastodon é um servidor de rede social <em>grátis, e open-source</em>. Uma alternativa <em>descentralizada</em> ás plataformas comerciais, que evita o risco de uma única empresa monopolizar a sua comunicação. Qualquer um pode ter uma instância Mastodon e assim participar na <em>rede social federada</em> sem problemas. about_mastodon: Mastodon é um servidor de rede social <em>grátis, e open-source</em>. Uma alternativa <em>descentralizada</em> ás plataformas comerciais, que evita o risco de uma única empresa monopolizar a sua comunicação. Qualquer um pode ter uma instância Mastodon e assim participar na <em>rede social federada</em> sem problemas.
about_this: Sobre essa instância
get_started: Como começar get_started: Como começar
apps: Aplicações
business_email: 'Email comercial:'
closed_registrations: Registros estão fechadas para essa instância.
contact: Contato
description_headline: O que é %{domain}?
domain_count_after: outras instâncias
domain_count_before: Conectado a
features:
api: Aberto para API de aplicações e serviços
blocks: Bloqueos e ferramentas para mudar
characters: 500 caracteres por post
chronology: Timeline são cronologicas
ethics: 'Design ético: sem propaganda, sem tracking'
gifv: GIFV e vídeos curtos
privacy: Granular, privacidade setada por post
public: Timelines públicas
features_headline: O que torna Mastodon diferente
get_started: Comece aqui
links: Links
source_code: Source code source_code: Source code
other_instances: Outras instâncias
terms: Termos terms: Termos
user_count_after: usuários
user_count_before: Lugar de
accounts: accounts:
follow: Seguir follow: Seguir
followers: Seguidores followers: Seguidores
following: Following following: Seguindo
nothing_here: Não há nada aqui! nothing_here: Não há nada aqui!
people_followed_by: Pessoas seguidas por %{name} people_followed_by: Pessoas seguidas por %{name}
people_who_follow: Pessoas que seguem %{name} people_who_follow: Pessoas que seguem %{name}
posts: Posts posts: Posts
remote_follow: Acesso remoto
unfollow: Unfollow unfollow: Unfollow
admin:
accounts:
are_you_sure: Você tem certeza?
display_name: Nome mostrado
domain: Domain
edit: Editar
email: E-mail
feed_url: URL do Feed
followers: Seguidores
follows: Seguindo
location:
all: Todos
local: Local
remote: Remoto
title: Local
media_attachments: Mídia anexadas
moderation:
all: Todos
silenced: Silenciado
suspended: Supenso
title: Moderação
most_recent_activity: Atividade mais recente
most_recent_ip: IP mais recente
not_subscribed: Não inscrito
order:
alphabetic: Alfabética
most_recent: Mais recente
title: Ordem
perform_full_suspension: Fazer suspensão completa
profile_url: URL do perfil
public: Público
push_subscription_expires: PuSH subscription expires
salmon_url: Salmon URL
silence: Silêncio
statuses: Status
title: Contas
undo_silenced: Desfazer silenciar
undo_suspension: Desfazer supensão
username: Usuário
web: Web
domain_block:
add_new: Adicionar nova
created_msg: Bloqueio do domínio está sendo processado
destroyed_msg: Bloqueio de domínio está sendo desfeito
domain: Domínio
application_mailer: application_mailer:
signature: notificações Mastodon de %{instance} signature: notificações Mastodon de %{instance}
auth: auth:
change_password: Mudar password change_password: Mudar senha
didnt_get_confirmation: Não recebeu instruções de confirmação? didnt_get_confirmation: Não recebeu instruções de confirmação?
forgot_password: Esqueceu a password? forgot_password: Esqueceu a senha?
login: Entrar login: Entrar
register: Registar register: Registar
resend_confirmation: Reenviar instruções de confirmação resend_confirmation: Reenviar instruções de confirmação
reset_password: Reset password reset_password: Resetar senha
set_new_password: Editar password set_new_password: Editar password
generic: generic:
changes_saved_msg: Mudanças guardadas! changes_saved_msg: Mudanças guardadas!

View file

@ -10,11 +10,13 @@ ja:
note: プロフィールは160文字まで設定することができます。 note: プロフィールは160文字まで設定することができます。
imports: imports:
data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい
sessions:
otp: 携帯電話に表示された2段階認証コードを入力するか、生成したリカバリーコードを使用してください。
labels: labels:
defaults: defaults:
avatar: アイコン avatar: アイコン
confirm_new_password: 新しいパスワード(確認用) confirm_new_password: 新しいパスワード(確認用)
confirm_password: 新しいパスワード confirm_password: パスワード(確認用)
current_password: 現在のパスワード current_password: 現在のパスワード
data: データ data: データ
display_name: 表示名 display_name: 表示名
@ -22,12 +24,13 @@ ja:
header: ヘッダー header: ヘッダー
locale: 言語 locale: 言語
locked: 非公開アカウントにする locked: 非公開アカウントにする
new_password: パスワード new_password: 新しいパスワード
note: プロフィール note: プロフィール
otp_attempt: 二段階認証コード otp_attempt: 二段階認証コード
password: パスワード password: パスワード
setting_boost_modal: ブーストする前に確認ダイアログを表示する setting_boost_modal: ブーストする前に確認ダイアログを表示する
setting_default_privacy: 投稿の公開範囲 setting_default_privacy: 投稿の公開範囲
severity: 重大性
type: インポートする項目 type: インポートする項目
username: ユーザー名 username: ユーザー名
interactions: interactions:

View file

@ -4,17 +4,17 @@ pt:
labels: labels:
defaults: defaults:
avatar: Avatar avatar: Avatar
confirm_new_password: Confirme nova password confirm_new_password: Confirme nova senha
confirm_password: Confirme a password confirm_password: Confirme a senha
current_password: Password atual current_password: Senha atual
display_name: Nome display_name: Nome
email: Endereço de email email: Endereço de email
header: Header header: Header
locale: Linguagem locale: Linguagem
new_password: Nova password new_password: Nova senha
note: Biografia note: Biografia
password: Password password: Senha
username: Username username: Usuário
interactions: interactions:
must_be_follower: Bloquear notificações de não-seguidores must_be_follower: Bloquear notificações de não-seguidores
must_be_following: Bloquear notificações de pessoas que você must_be_following: Bloquear notificações de pessoas que você

View file

@ -78,7 +78,7 @@ Rails.application.routes.draw do
namespace :admin do namespace :admin do
resources :pubsubhubbub, only: [:index] resources :pubsubhubbub, only: [:index]
resources :domain_blocks, only: [:index, :new, :create] resources :domain_blocks, only: [:index, :new, :create, :show, :destroy]
resources :settings, only: [:index, :update] resources :settings, only: [:index, :update]
resources :reports, only: [:index, :show, :update] do resources :reports, only: [:index, :show, :update] do

View file

@ -1,6 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
namespace :mastodon do namespace :mastodon do
desc 'Execute daily tasks'
task :daily do
Rake::Task['mastodon:feeds:clear'].invoke
Rake::Task['mastodon:media:clear'].invoke
Rake::Task['mastodon:users:clear'].invoke
Rake::Task['mastodon:push:refresh'].invoke
end
desc 'Turn a user into an admin, identified by the USERNAME environment variable'
task make_admin: :environment do task make_admin: :environment do
include RoutingHelper include RoutingHelper
@ -13,12 +23,13 @@ namespace :mastodon do
desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.' desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.'
task confirm_email: :environment do task confirm_email: :environment do
email = ENV.fetch('USER_EMAIL') email = ENV.fetch('USER_EMAIL')
user = User.where(email: email).first user = User.find_by(email: email)
if user if user
user.update(confirmed_at: Time.now.utc) user.update(confirmed_at: Time.now.utc)
puts "User #{email} confirmed." puts "#{email} confirmed"
else else
abort "User #{email} not found." abort "#{email} not found"
end end
end end
@ -32,6 +43,13 @@ namespace :mastodon do
task remove_silenced: :environment do task remove_silenced: :environment do
MediaAttachment.where(account: Account.silenced).find_each(&:destroy) MediaAttachment.where(account: Account.silenced).find_each(&:destroy)
end end
desc 'Remove cached remote media attachments that are older than a week'
task remove_remote: :environment do
MediaAttachment.where.not(remote_url: '').where('created_at < ?', 1.week.ago).find_each do |media|
media.file.destroy
end
end
end end
namespace :push do namespace :push do
@ -60,7 +78,7 @@ namespace :mastodon do
end end
end end
desc 'Clears all timelines so that they would be regenerated on next hit' desc 'Clears all timelines'
task clear_all: :environment do task clear_all: :environment do
Redis.current.keys('feed:*').each { |key| Redis.current.del(key) } Redis.current.keys('feed:*').each { |key| Redis.current.del(key) }
end end
@ -126,8 +144,13 @@ namespace :mastodon do
Rails.logger.debug 'Generating static avatars/headers for GIF ones...' Rails.logger.debug 'Generating static avatars/headers for GIF ones...'
Account.unscoped.where(avatar_content_type: 'image/gif').or(Account.unscoped.where(header_content_type: 'image/gif')).find_each do |account| Account.unscoped.where(avatar_content_type: 'image/gif').or(Account.unscoped.where(header_content_type: 'image/gif')).find_each do |account|
account.avatar.reprocess! begin
account.header.reprocess! account.avatar.reprocess!
account.header.reprocess!
rescue StandardError => e
Rails.logger.error "Error while generating static avatars/headers for account #{account.id}: #{e}"
next
end
end end
Rails.logger.debug 'Done!' Rails.logger.debug 'Done!'

View file

@ -1,5 +1,6 @@
{ {
"name": "mastodon", "name": "mastodon",
"license" : "AGPL-3.0",
"scripts": { "scripts": {
"start": "babel-node ./streaming/index.js --presets es2015,stage-2", "start": "babel-node ./streaming/index.js --presets es2015,stage-2",
"storybook": "start-storybook -p 9001 -c storybook", "storybook": "start-storybook -p 9001 -c storybook",

View file

@ -61,5 +61,4 @@ describe 'stream_entries/show.html.haml' do
expect(mf2.entry.in_reply_to.format.author.format.name.to_s).to eq alice.display_name expect(mf2.entry.in_reply_to.format.author.format.name.to_s).to eq alice.display_name
expect(mf2.entry.in_reply_to.format.author.format.url.to_s).not_to be_empty expect(mf2.entry.in_reply_to.format.author.format.url.to_s).not_to be_empty
end end
end end