Merge branch 'master' into fix/cache_blocking

th-downstream
Effy Elden 8 years ago committed by GitHub
commit d52abe1cc7

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

@ -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'

@ -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

@ -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).

@ -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

@ -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
} }

@ -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",

@ -1,121 +1,125 @@
const ja = { const ja = {
"column_back_button.label": "戻る",
"lightbox.close": "閉じる",
"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.unblock": "@{name} さんのブロックを解除",
"account.unfollow": "フォロー解除",
"account.block": "@{name} さんをブロック", "account.block": "@{name} さんをブロック",
"account.mute": "ミュート", "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
"account.unmute": "ミュート解除", "account.edit_profile": "プロフィールを編集",
"account.follow": "フォロー", "account.follow": "フォロー",
"account.report": "@{name}を通報する",
"account.posts": "投稿",
"account.follows": "フォロー",
"account.followers": "フォロワー", "account.followers": "フォロワー",
"account.follows": "フォロー",
"account.follows_you": "フォローされています", "account.follows_you": "フォローされています",
"account.mention": "@{name} さんに返信",
"account.mute": "ミュート",
"account.posts": "投稿",
"account.report": "@{name}を通報する",
"account.requested": "承認待ち", "account.requested": "承認待ち",
"follow_request.authorize": "許可", "account.unblock": "@{name} さんのブロックを解除",
"follow_request.reject": "拒否", "account.unfollow": "フォロー解除",
"getting_started.heading": "スタート", "account.unmute": "ミュート解除",
"getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。", "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
"getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。", "column.blocks": "ブロックしたユーザー",
"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": "公開", "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
"privacy.public.long": "公開TLに投稿する", "empty_column.home.public_timeline": "連合タイムライン",
"privacy.unlisted.short": "未収載", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"privacy.unlisted.long": "公開TLで表示しない", "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
"privacy.private.short": "非公開", "follow_request.authorize": "許可",
"privacy.private.long": "フォロワーだけに公開", "follow_request.reject": "拒否",
"privacy.direct.short": "ダイレクト", "getting_started.apps": "さまざまなアプリで利用できます。",
"privacy.direct.long": "含んだユーザーだけに公開", "getting_started.heading": "スタート",
"privacy.change": "投稿のプライバシーを変更", "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub{github})から開発に参加したり、問題を報告したりできます。 {apps}",
"report.heading": "新規通報", "home.column_settings.advanced": "上級者向け",
"report.placeholder": "コメント", "home.column_settings.basic": "シンプル",
"report.target": "問題のユーザー", "home.column_settings.filter_regex": "正規表現でフィルター",
"report.submit": "通報する", "home.column_settings.show_reblogs": "ブースト表示",
"navigation_bar.edit_profile": "プロフィールを編集", "home.column_settings.show_replies": "返信表示",
"navigation_bar.preferences": "ユーザー設定", "home.settings": "カラム設定",
"lightbox.close": "閉じる",
"loading_indicator.label": "読み込み中...",
"media_gallery.toggle_visible": "表示切り替え",
"missing_indicator.label": "見つかりません",
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.community_timeline": "ローカルタイムライン", "navigation_bar.community_timeline": "ローカルタイムライン",
"navigation_bar.public_timeline": "連合タイムライン", "navigation_bar.edit_profile": "プロフィールを編集",
"navigation_bar.logout": "ログアウト",
"navigation_bar.favourites": "お気に入り", "navigation_bar.favourites": "お気に入り",
"navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.follow_requests": "フォローリクエスト",
"navigation_bar.info": "サーバー情報", "navigation_bar.info": "サーバー情報",
"reply_indicator.cancel": "キャンセル", "navigation_bar.logout": "ログアウト",
"search.placeholder": "検索", "navigation_bar.mutes": "ミュートしたユーザー",
"search.account": "アカウント", "navigation_bar.preferences": "ユーザー設定",
"search.hashtag": "ハッシュタグ", "navigation_bar.public_timeline": "連合タイムライン",
"search.status_by": "{uuuname}からの投稿",
"search_results.total": "{count} 件",
"upload_area.title": "ファイルをこちらにドラッグしてください",
"upload_button.label": "メディアを追加",
"upload_form.undo": "やり直す",
"notification.follow": "{name} さんにフォローされました",
"notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました", "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
"notification.reblog": "{name} さんがあなたのトゥートをブーストしました", "notification.follow": "{name} さんにフォローされました",
"notification.mention": "{name} さんがあなたに返信しました", "notification.mention": "{name} さんがあなたに返信しました",
"notifications.clear": "通知を片付ける", "notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
"notifications.clear_confirmation": "通知を全部片付けます。大丈夫ですか?", "notifications.clear": "通知を消去",
"notifications.clear_confirmation": "本当に通知を消去しますか?",
"notifications.column_settings.alert": "デスクトップ通知", "notifications.column_settings.alert": "デスクトップ通知",
"notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.follow": "新しいフォロワー",
"notifications.column_settings.favourite": "お気に入り", "notifications.column_settings.favourite": "お気に入り",
"notifications.column_settings.follow": "新しいフォロワー",
"notifications.column_settings.mention": "返信", "notifications.column_settings.mention": "返信",
"notifications.column_settings.reblog": "ブースト", "notifications.column_settings.reblog": "ブースト",
"notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.sound": "通知音を再生", "notifications.column_settings.sound": "通知音を再生",
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
"empty_column.home.public_timeline": "連合タイムライン",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
"empty_column.hashtag": "このハッシュタグはまだ使っていません。",
"upload_progress.label": "アップロード中…",
"emoji_button.label": "絵文字を追加",
"home.column_settings.basic": "シンプル",
"home.column_settings.advanced": "エキスパート",
"home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示",
"home.column_settings.filter_regex": "正規表現でフィルター",
"home.settings": "カラム設定",
"notifications.settings": "カラム設定", "notifications.settings": "カラム設定",
"missing_indicator.label": "見つかりません", "privacy.change": "投稿のプライバシーを変更",
"boost_modal.combo": "次は{combo}を押せば、これをスキップできます。" "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;

@ -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)));
}; };

@ -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 {

@ -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

@ -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

@ -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)

@ -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
enum type: [:following, :blocking, :muting] belongs_to :account, required: true
belongs_to :account enum type: [:following, :blocking, :muting]
FILE_TYPES = ['text/plain', 'text/csv'].freeze validates :type, presence: true
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

@ -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)

@ -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

@ -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)]

@ -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']

@ -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

@ -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

@ -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

@ -71,6 +71,6 @@
%p Dette dokumentet er lisensiert under CC-BY-SA. De ble sist oppdatert 12. april 2017. %p Dette dokumentet er lisensiert under CC-BY-SA. De ble sist oppdatert 12. april 2017.
%p %p
Dokumentet er en adoptert og endret versjon fra Dokumentet er en adoptert og endret versjon fra
= succeed '.' do = succeed '.' do
= link_to 'Discourse privacy policy', 'https://github.com/discourse/discourse' = link_to 'Discourse privacy policy', 'https://github.com/discourse/discourse'

@ -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

@ -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?

@ -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?

@ -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

@ -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'

@ -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

@ -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

@ -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 }

@ -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

@ -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

@ -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

@ -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 }/

@ -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' }/

@ -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' }/

@ -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")

@ -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')

@ -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

@ -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: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。

@ -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!

@ -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:

@ -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ê

@ -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

@ -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!'

@ -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",

@ -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

Loading…
Cancel
Save