Merge branch 'master' into fix/cache_blocking
This commit is contained in:
		| @@ -14,6 +14,7 @@ addons: | ||||
|   postgresql: 9.4 | ||||
|  | ||||
| rvm: | ||||
|   - 2.3.4 | ||||
|   - 2.4.1 | ||||
|  | ||||
| services: | ||||
|   | ||||
							
								
								
									
										4
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| source 'https://rubygems.org' | ||||
| ruby '2.4.1' | ||||
| ruby '>= 2.3.0', '< 2.5.0' | ||||
|  | ||||
| gem 'pkg-config' | ||||
|  | ||||
| @@ -88,7 +88,7 @@ group :development do | ||||
|   gem 'bullet' | ||||
|   gem 'active_record_query_trace' | ||||
|  | ||||
|   gem 'capistrano' | ||||
|   gem 'capistrano', '3.8.0' | ||||
|   gem 'capistrano-rails' | ||||
|   gem 'capistrano-rbenv' | ||||
|   gem 'capistrano-yarn' | ||||
|   | ||||
| @@ -41,7 +41,7 @@ GEM | ||||
|       tzinfo (~> 1.1) | ||||
|     addressable (2.5.1) | ||||
|       public_suffix (~> 2.0, >= 2.0.2) | ||||
|     airbrussh (1.1.2) | ||||
|     airbrussh (1.2.0) | ||||
|       sshkit (>= 1.6.1, != 1.7.0) | ||||
|     arel (7.1.4) | ||||
|     ast (2.3.0) | ||||
| @@ -469,7 +469,7 @@ DEPENDENCIES | ||||
|   binding_of_caller | ||||
|   browserify-rails | ||||
|   bullet | ||||
|   capistrano | ||||
|   capistrano (= 3.8.0) | ||||
|   capistrano-faster-assets (~> 1.0) | ||||
|   capistrano-rails | ||||
|   capistrano-rbenv | ||||
|   | ||||
| @@ -3,3 +3,4 @@ | ||||
| * * * * | ||||
|  | ||||
| - [ ] I searched or browsed the repo’s 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** | ||||
|   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 | ||||
|  | ||||
| - `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) { | ||||
|     try { | ||||
|       const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i'); | ||||
|       showStatus = showStatus && !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'content']) : statusForId.get('content')); | ||||
|       if (showStatus) { | ||||
|         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) { | ||||
|       // Bad regex, don't affect filters | ||||
|     } | ||||
|   | ||||
| @@ -75,6 +75,7 @@ const fr = { | ||||
|   "navigation_bar.favourites": "Favoris", | ||||
|   "navigation_bar.info": "Plus d'informations", | ||||
|   "navigation_bar.logout": "Déconnexion", | ||||
|   "navigation_bar.mutes": "Utilisateurs muets", | ||||
|   "navigation_bar.follow_requests": "Demandes de suivi", | ||||
|   "reply_indicator.cancel": "Annuler", | ||||
|   "search.placeholder": "Rechercher", | ||||
|   | ||||
| @@ -1,121 +1,125 @@ | ||||
| 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.block": "@{name} さんをブロック", | ||||
|   "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。", | ||||
|   "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.unfollow": "フォロー解除", | ||||
|   "account.block": "@{name} さんをブロック", | ||||
|   "account.mute": "ミュート", | ||||
|   "account.unmute": "ミュート解除", | ||||
|   "account.follow": "フォロー", | ||||
|   "account.report": "@{name}を通報する", | ||||
|   "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": "ホーム", | ||||
|   "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。", | ||||
|   "column.blocks": "ブロックしたユーザー", | ||||
|   "column.community": "ローカルタイムライン", | ||||
|   "column.public": "連合タイムライン", | ||||
|   "column.notifications": "通知", | ||||
|   "column.favourites": "お気に入り", | ||||
|   "tabs_bar.compose": "投稿", | ||||
|   "tabs_bar.home": "ホーム", | ||||
|   "tabs_bar.mentions": "返信", | ||||
|   "tabs_bar.local_timeline": "ローカル", | ||||
|   "tabs_bar.federated_timeline": "連合", | ||||
|   "tabs_bar.notifications": "通知", | ||||
|   "column.follow_requests": "フォローリクエスト", | ||||
|   "column.home": "ホーム", | ||||
|   "column.mutes": "ミュートしたユーザー", | ||||
|   "column.notifications": "通知", | ||||
|   "column.public": "連合タイムライン", | ||||
|   "column_back_button.label": "戻る", | ||||
|   "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.sensitive": "メディアを不適切なコンテンツとしてマークする", | ||||
|   "compose_form.spoiler": "テキストを隠す", | ||||
|   "compose_form.spoiler_placeholder": "内容注意メッセージ", | ||||
|   "compose_form.private": "非公開にする", | ||||
|   "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.unlisted": "公開タイムラインに表示しない", | ||||
|   "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": "通知音を再生", | ||||
|   "compose_form.spoiler_placeholder": "閲覧注意", | ||||
|   "emoji_button.label": "絵文字を追加", | ||||
|   "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", | ||||
|   "empty_column.hashtag": "このハッシュタグはまだ使われていません。", | ||||
|   "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", | ||||
|   "empty_column.home.public_timeline": "連合タイムライン", | ||||
|   "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", | ||||
|   "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!", | ||||
|   "empty_column.hashtag": "このハッシュタグはまだ使っていません。", | ||||
|   "upload_progress.label": "アップロード中…", | ||||
|   "emoji_button.label": "絵文字を追加", | ||||
|   "follow_request.authorize": "許可", | ||||
|   "follow_request.reject": "拒否", | ||||
|   "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.advanced": "エキスパート", | ||||
|   "home.column_settings.filter_regex": "正規表現でフィルター", | ||||
|   "home.column_settings.show_reblogs": "ブースト表示", | ||||
|   "home.column_settings.show_replies": "返信表示", | ||||
|   "home.column_settings.filter_regex": "正規表現でフィルター", | ||||
|   "home.settings": "カラム設定", | ||||
|   "notifications.settings": "カラム設定", | ||||
|   "lightbox.close": "閉じる", | ||||
|   "loading_indicator.label": "読み込み中...", | ||||
|   "media_gallery.toggle_visible": "表示切り替え", | ||||
|   "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; | ||||
|   | ||||
| @@ -48,6 +48,9 @@ const normalizeStatus = (state, status) => { | ||||
|     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))); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1203,6 +1203,10 @@ a.status__content__spoiler-link { | ||||
|   &:focus { | ||||
|     outline: 0; | ||||
|   } | ||||
|  | ||||
|   @media screen and (max-width: 600px) { | ||||
|     font-size: 16px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .spoiler-input__input { | ||||
| @@ -1267,6 +1271,10 @@ a.status__content__spoiler-link { | ||||
|     color: $color5; | ||||
|     border-bottom-color: $color4; | ||||
|   } | ||||
|  | ||||
|   @media screen and (max-width: 600px) { | ||||
|     font-size: 16px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @import 'boost'; | ||||
| @@ -1906,6 +1914,10 @@ button.icon-button.active i.fa-retweet { | ||||
|   &:focus { | ||||
|     background: lighten($color1, 4%); | ||||
|   } | ||||
|  | ||||
|   @media screen and (max-width: 600px) { | ||||
|     font-size: 16px; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .search__icon { | ||||
|   | ||||
| @@ -15,16 +15,26 @@ module Admin | ||||
|  | ||||
|       if @domain_block.save | ||||
|         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 | ||||
|         render action: :new | ||||
|       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 | ||||
|  | ||||
|     def resource_params | ||||
|       params.require(:domain_block).permit(:domain, :severity) | ||||
|       params.require(:domain_block).permit(:domain, :severity, :reject_media, :retroactive) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -8,7 +8,9 @@ class ApplicationController < ActionController::Base | ||||
|   force_ssl if: "Rails.env.production? && ENV['LOCAL_HTTPS'] == 'true'" | ||||
|  | ||||
|   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 ActiveRecord::RecordNotFound, with: :not_found | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| class DomainBlock < ApplicationRecord | ||||
|   enum severity: [:silence, :suspend] | ||||
|  | ||||
|   attr_accessor :retroactive | ||||
|  | ||||
|   validates :domain, presence: true, uniqueness: true | ||||
|  | ||||
|   def self.blocked?(domain) | ||||
|   | ||||
| @@ -1,13 +1,15 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Import < ApplicationRecord | ||||
|   FILE_TYPES = ['text/plain', 'text/csv'].freeze | ||||
|  | ||||
|   self.inheritance_column = false | ||||
|  | ||||
|   belongs_to :account, required: true | ||||
|  | ||||
|   enum type: [:following, :blocking, :muting] | ||||
|  | ||||
|   belongs_to :account | ||||
|  | ||||
|   FILE_TYPES = ['text/plain', 'text/csv'].freeze | ||||
|   validates :type, presence: true | ||||
|  | ||||
|   has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV['PAPERCLIP_SECRET'] | ||||
|   validates_attachment_content_type :data, content_type: FILE_TYPES | ||||
|   | ||||
| @@ -110,6 +110,10 @@ class Status < ApplicationRecord | ||||
|     results | ||||
|   end | ||||
|  | ||||
|   def non_sensitive_with_media? | ||||
|     !sensitive? && media_attachments.any? | ||||
|   end | ||||
|  | ||||
|   class << self | ||||
|     def as_home_timeline(account) | ||||
|       where(account: [account] + account.following) | ||||
|   | ||||
| @@ -3,12 +3,34 @@ | ||||
| class BlockDomainService < BaseService | ||||
|   def call(domain_block) | ||||
|     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 | ||||
|       Account.where(domain: domain_block.domain).find_each do |account| | ||||
|         account.subscription(api_subscription_url(account.id)).unsubscribe if account.subscribed? | ||||
|         SuspendAccountService.new.call(account) | ||||
|       end | ||||
|       suspend_accounts!(domain_block.domain) | ||||
|     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 | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class FollowRemoteAccountService < BaseService | ||||
|     return Account.find_local(username) if TagManager.instance.local_domain?(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}" | ||||
|  | ||||
| @@ -62,6 +62,10 @@ class FollowRemoteAccountService < BaseService | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def account_needs_webfinger_update?(account) | ||||
|     account&.last_webfingered_at.nil? || account.last_webfingered_at <= 1.day.ago | ||||
|   end | ||||
|  | ||||
|   def get_feed(url) | ||||
|     response = http_client.get(Addressable::URI.parse(url)) | ||||
|     [response.to_s, Nokogiri::XML(response)] | ||||
|   | ||||
| @@ -179,12 +179,12 @@ class ProcessFeedService < BaseService | ||||
|     end | ||||
|  | ||||
|     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) | ||||
|     end | ||||
|  | ||||
|     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| | ||||
|         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']) | ||||
|         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 | ||||
|           media.file_remote_url = link['href'] | ||||
|   | ||||
| @@ -13,6 +13,7 @@ class SuspendAccountService < BaseService | ||||
|  | ||||
|   def purge_content | ||||
|     @account.statuses.reorder(nil).find_each do |status| | ||||
|       # This federates out deletes to previous followers | ||||
|       RemoveStatusService.new.call(status) | ||||
|     end | ||||
|  | ||||
| @@ -29,9 +30,7 @@ class SuspendAccountService < BaseService | ||||
|     @account.display_name = '' | ||||
|     @account.note         = '' | ||||
|     @account.avatar.destroy | ||||
|     @account.avatar.clear | ||||
|     @account.header.destroy | ||||
|     @account.header.clear | ||||
|     @account.save! | ||||
|   end | ||||
|  | ||||
|   | ||||
							
								
								
									
										15
									
								
								app/services/unblock_domain_service.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/services/unblock_domain_service.rb
									
									
									
									
									
										Normal 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 | ||||
| @@ -6,6 +6,7 @@ class UnfollowService < BaseService | ||||
|   # @param [Account] target_account Which to unfollow | ||||
|   def call(source_account, 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? | ||||
|     UnmergeWorker.perform_async(target_account.id, source_account.id) | ||||
|   end | ||||
|   | ||||
| @@ -71,6 +71,6 @@ | ||||
|   %p Dette dokumentet er lisensiert under CC-BY-SA. De ble sist oppdatert 12. april 2017. | ||||
|  | ||||
|   %p | ||||
|     Dokumentet er en adoptert og endret versjon fra  | ||||
|     Dokumentet er en adoptert og endret versjon fra | ||||
|     = succeed '.' do | ||||
|       = 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)})" } | ||||
|   - if user_signed_in? && current_account.id != @account.id && !current_account.requested?(@account) | ||||
| .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) | ||||
|     .controls | ||||
|       - if current_account.following?(@account) | ||||
|         = link_to t('accounts.unfollow'), unfollow_account_path(@account), data: { method: :post }, class: 'button' | ||||
|       - if current_account.following?(account) | ||||
|         = link_to t('accounts.unfollow'), unfollow_account_path(account), data: { method: :post }, class: 'button' | ||||
|       - 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? | ||||
|     .controls | ||||
|       .remote-follow | ||||
|         = link_to t('accounts.remote_follow'), account_remote_follow_path(@account), class: 'button' | ||||
|   .avatar= image_tag @account.avatar.url(:original), class: 'u-photo' | ||||
|         = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button' | ||||
|   .avatar= image_tag account.avatar.url(:original), class: 'u-photo' | ||||
|   %h1.name | ||||
|     %span.p-name.emojify= display_name(@account) | ||||
|     %span.p-name.emojify= display_name(account) | ||||
|     %small | ||||
|       %span= "@#{@account.username}" | ||||
|       = fa_icon('lock') if @account.locked? | ||||
|       %span= "@#{account.username}" | ||||
|       = fa_icon('lock') if account.locked? | ||||
|   .details | ||||
|     .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 | ||||
|       .counter{ class: active_nav_class(short_account_url(@account)) } | ||||
|         = link_to short_account_url(@account), class: 'u-url u-uid' do | ||||
|       .counter{ class: active_nav_class(short_account_url(account)) } | ||||
|         = link_to short_account_url(account), class: 'u-url u-uid' do | ||||
|           %span.counter-label= t('accounts.posts') | ||||
|           %span.counter-number= number_with_delimiter @account.statuses_count | ||||
|       .counter{ class: active_nav_class(following_account_url(@account)) } | ||||
|         = link_to following_account_url(@account) do | ||||
|           %span.counter-number= number_with_delimiter account.statuses_count | ||||
|       .counter{ class: active_nav_class(following_account_url(account)) } | ||||
|         = link_to following_account_url(account) do | ||||
|           %span.counter-label= t('accounts.following') | ||||
|           %span.counter-number= number_with_delimiter @account.following_count | ||||
|       .counter{ class: active_nav_class(followers_account_url(@account)) } | ||||
|         = link_to followers_account_url(@account) do | ||||
|           %span.counter-number= number_with_delimiter account.following_count | ||||
|       .counter{ class: active_nav_class(followers_account_url(account)) } | ||||
|         = link_to followers_account_url(account) do | ||||
|           %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 | ||||
|   = t('accounts.people_who_follow', name: display_name(@account)) | ||||
|  | ||||
| = render partial: 'header' | ||||
| = render 'header', account: @account | ||||
|  | ||||
| .accounts-grid | ||||
|   - if @followers.empty? | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| - content_for :page_title do | ||||
|   = t('accounts.people_followed_by', name: display_name(@account)) | ||||
|  | ||||
| = render partial: 'header' | ||||
| = render 'header', account: @account | ||||
|  | ||||
| .accounts-grid | ||||
|   - if @following.empty? | ||||
|   | ||||
| @@ -20,7 +20,7 @@ | ||||
| .h-feed | ||||
|   %data.p-name{ value: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/ | ||||
|  | ||||
|   = render partial: 'header' | ||||
|   = render 'header', account: @account | ||||
|  | ||||
|   - if @statuses.empty? | ||||
|     .accounts-grid | ||||
|   | ||||
| @@ -6,12 +6,19 @@ | ||||
|     %tr | ||||
|       %th= t('admin.domain_block.domain') | ||||
|       %th= t('admin.domain_block.severity') | ||||
|       %th= t('admin.domain_block.reject_media') | ||||
|       %th | ||||
|   %tbody | ||||
|     - @blocks.each do |block| | ||||
|       %tr | ||||
|         %td | ||||
|           %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 | ||||
| = 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}") } | ||||
|  | ||||
|   %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 | ||||
|     = f.button :button, t('admin.domain_block.new.create'), type: :submit | ||||
|   | ||||
							
								
								
									
										9
									
								
								app/views/admin/domain_blocks/show.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/views/admin/domain_blocks/show.html.haml
									
									
									
									
									
										Normal 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 | ||||
| @@ -1,5 +1,5 @@ | ||||
| attributes :id, :remote_url, :type | ||||
|  | ||||
| node(:url)         { |media| full_asset_url(media.file.url(:original)) } | ||||
| node(:preview_url) { |media| full_asset_url(media.file.url(:small)) } | ||||
| node(:url)         { |media| media.file.blank? ? media.remote_url : full_asset_url(media.file.url(:original)) } | ||||
| 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 } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| %p.hint= t('two_factor_auth.recovery_instructions') | ||||
|  | ||||
| %ol.recovery-codes | ||||
|   - @codes.each do |code| | ||||
|   - recovery_codes.each do |code| | ||||
|     %li | ||||
|       %samp= code | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| - content_for :page_title do | ||||
|   = t('settings.two_factor_auth') | ||||
|  | ||||
| = render 'recovery_codes' | ||||
| = render partial: 'recovery_codes', object: @codes | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| - content_for :page_title do | ||||
|   = t('settings.two_factor_auth') | ||||
|  | ||||
| = render 'recovery_codes' | ||||
| = render partial: 'recovery_codes', object: @codes | ||||
|   | ||||
							
								
								
									
										4
									
								
								app/views/stream_entries/_og_description.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								app/views/stream_entries/_og_description.html.haml
									
									
									
									
									
										Normal 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 }/ | ||||
							
								
								
									
										6
									
								
								app/views/stream_entries/_og_image.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/views/stream_entries/_og_image.html.haml
									
									
									
									
									
										Normal 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' }/ | ||||
| @@ -6,17 +6,8 @@ | ||||
|   %meta{ property: 'og:type', content: 'article' }/ | ||||
|   %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? | ||||
|     %meta{ property: 'og:description', content: @stream_entry.activity.spoiler_text }/ | ||||
|   - 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' }/ | ||||
|   = render 'stream_entries/og_description', activity: @stream_entry.activity | ||||
|   = render 'stream_entries/og_image', activity: @stream_entry.activity, account: @account | ||||
|  | ||||
|   %meta{ property: 'twitter:card', content: 'summary' }/ | ||||
|  | ||||
|   | ||||
							
								
								
									
										17
									
								
								bin/rspec
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										17
									
								
								bin/rspec
									
									
									
									
									
										Executable 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") | ||||
| @@ -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 :branch, ENV.fetch('BRANCH', 'master') | ||||
|  | ||||
|   | ||||
| @@ -81,6 +81,8 @@ en: | ||||
|       web: Web | ||||
|     domain_block: | ||||
|       add_new: Add new | ||||
|       created_msg: Domain block is now being processed | ||||
|       destroyed_msg: Domain block has been undone | ||||
|       domain: Domain | ||||
|       new: | ||||
|         create: Create block | ||||
| @@ -90,8 +92,22 @@ en: | ||||
|           silence: Silence | ||||
|           suspend: Suspend | ||||
|         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 | ||||
|       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 | ||||
|       undo: Undo | ||||
|     pubsubhubbub: | ||||
|       callback_url: Callback URL | ||||
|       confirmed: Confirmed | ||||
|   | ||||
| @@ -71,6 +71,7 @@ ja: | ||||
|       profile_url: プロフィールURL | ||||
|       public: パブリック | ||||
|       push_subscription_expires: PuSH購読期限切れ | ||||
|       reset_password: パスワード再設定 | ||||
|       salmon_url: Salmon URL | ||||
|       silence: サイレンス | ||||
|       statuses: トゥート数 | ||||
| @@ -81,6 +82,8 @@ ja: | ||||
|       web: Web | ||||
|     domain_block: | ||||
|       add_new: 新規追加 | ||||
|       created_msg: ドメインブロック処理を完了しました | ||||
|       destroyed_msg: ドメインブロックを外しました | ||||
|       domain: ドメイン | ||||
|       new: | ||||
|         create: ブロックを作成 | ||||
| @@ -90,8 +93,21 @@ ja: | ||||
|           silence: サイレンス | ||||
|           suspend: 停止 | ||||
|         title: 新規ドメインブロック | ||||
|       reject_media: メディアファイルを拒否 | ||||
|       reject_media_hint: ローカルに保村されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です。 | ||||
|       severities: | ||||
|         silence: サイレンス | ||||
|         suspend: 停止 | ||||
|       severity: 深刻度 | ||||
|       show: | ||||
|         affected_accounts: "データベース中の%{count}個のアカウントに影響します" | ||||
|         retroactive: | ||||
|           silence: このドメインからの存在するすべてのアカウントのサイレンスを戻す | ||||
|           suspend: このドメインからの存在するすべてのアカウントの停止を戻す | ||||
|         title: "%{domain}のドメインブロックを戻す" | ||||
|         undo: 元に戻す | ||||
|       title: ドメインブロック | ||||
|       undo: 元に戻す | ||||
|     pubsubhubbub: | ||||
|       callback_url: コールバックURL | ||||
|       confirmed: 確認済み | ||||
| @@ -106,7 +122,7 @@ ja: | ||||
|       delete: 削除 | ||||
|       id: ID | ||||
|       mark_as_resolved: 解決済みとしてマーク | ||||
|       report: 'レポート#%{id}' | ||||
|       report: レポート#%{id} | ||||
|       reported_account: 報告対象アカウント | ||||
|       reported_by: 報告者 | ||||
|       resolved: 解決済み | ||||
| @@ -290,8 +306,13 @@ ja: | ||||
|     disable: 無効 | ||||
|     enable: 有効 | ||||
|     enabled_success: 二段階認証が有効になりました | ||||
|     generate_recovery_codes: 復元コードを生成 | ||||
|     instructions_html: "<strong>Google Authenticatorか、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。</strong>これ以降、ログインするときはそのアプリで生成されるコードが必要になります。" | ||||
|     lost_recovery_codes: リカバリコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリコードは無効になります。 | ||||
|     manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:' | ||||
|     recovery_codes: リカバリーコード | ||||
|     recovery_codes_regenerated: リカバリーコードが再生成されました。 | ||||
|     recovery_instructions: 携帯電話を紛失した場合、以下の内どれかのリカバリコードを使用してアカウントへアクセスすることができます。 リカバリコードは印刷して安全に保管してください。 | ||||
|     setup: 初期設定 | ||||
|     warning: 現在認証アプリを設定できない場合、無効に設定して、有効にしないでください。 | ||||
|     wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。 | ||||
|   | ||||
| @@ -2,28 +2,97 @@ | ||||
| pt: | ||||
|   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_this: Sobre essa instância | ||||
|     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 | ||||
|     other_instances: Outras instâncias | ||||
|     terms: Termos | ||||
|     user_count_after: usuários | ||||
|     user_count_before: Lugar de | ||||
|   accounts: | ||||
|     follow: Seguir | ||||
|     followers: Seguidores | ||||
|     following: Following | ||||
|     following: Seguindo | ||||
|     nothing_here: Não há nada aqui! | ||||
|     people_followed_by: Pessoas seguidas por %{name} | ||||
|     people_who_follow: Pessoas que seguem %{name} | ||||
|     posts: Posts | ||||
|     remote_follow: Acesso remoto | ||||
|     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: | ||||
|     signature: notificações Mastodon de %{instance} | ||||
|   auth: | ||||
|     change_password: Mudar password | ||||
|     change_password: Mudar senha | ||||
|     didnt_get_confirmation: Não recebeu instruções de confirmação? | ||||
|     forgot_password: Esqueceu a password? | ||||
|     forgot_password: Esqueceu a senha? | ||||
|     login: Entrar | ||||
|     register: Registar | ||||
|     resend_confirmation: Reenviar instruções de confirmação | ||||
|     reset_password: Reset password | ||||
|     reset_password: Resetar senha | ||||
|     set_new_password: Editar password | ||||
|   generic: | ||||
|     changes_saved_msg: Mudanças guardadas! | ||||
|   | ||||
| @@ -10,11 +10,13 @@ ja: | ||||
|         note: プロフィールは160文字まで設定することができます。 | ||||
|       imports: | ||||
|         data: 他の Mastodon サーバーからエクスポートしたCSVファイルを選択して下さい | ||||
|       sessions: | ||||
|         otp: 携帯電話に表示された2段階認証コードを入力するか、生成したリカバリーコードを使用してください。 | ||||
|     labels: | ||||
|       defaults: | ||||
|         avatar: アイコン | ||||
|         confirm_new_password: 新しいパスワード(確認用) | ||||
|         confirm_password: 新しいパスワード | ||||
|         confirm_password: パスワード(確認用) | ||||
|         current_password: 現在のパスワード | ||||
|         data: データ | ||||
|         display_name: 表示名 | ||||
| @@ -22,12 +24,13 @@ ja: | ||||
|         header: ヘッダー | ||||
|         locale: 言語 | ||||
|         locked: 非公開アカウントにする | ||||
|         new_password: パスワード | ||||
|         new_password: 新しいパスワード | ||||
|         note: プロフィール | ||||
|         otp_attempt: 二段階認証コード | ||||
|         password: パスワード | ||||
|         setting_boost_modal: ブーストする前に確認ダイアログを表示する | ||||
|         setting_default_privacy: 投稿の公開範囲 | ||||
|         severity: 重大性 | ||||
|         type: インポートする項目 | ||||
|         username: ユーザー名 | ||||
|       interactions: | ||||
|   | ||||
| @@ -4,17 +4,17 @@ pt: | ||||
|     labels: | ||||
|       defaults: | ||||
|         avatar: Avatar | ||||
|         confirm_new_password: Confirme nova password | ||||
|         confirm_password: Confirme a password | ||||
|         current_password: Password atual | ||||
|         confirm_new_password: Confirme nova senha | ||||
|         confirm_password: Confirme a senha | ||||
|         current_password: Senha atual | ||||
|         display_name: Nome | ||||
|         email: Endereço de email | ||||
|         header: Header | ||||
|         locale: Linguagem | ||||
|         new_password: Nova password | ||||
|         new_password: Nova senha | ||||
|         note: Biografia | ||||
|         password: Password | ||||
|         username: Username | ||||
|         password: Senha | ||||
|         username: Usuário | ||||
|       interactions: | ||||
|         must_be_follower: Bloquear notificações de não-seguidores | ||||
|         must_be_following: Bloquear notificações de pessoas que você | ||||
|   | ||||
| @@ -78,7 +78,7 @@ Rails.application.routes.draw do | ||||
|  | ||||
|   namespace :admin do | ||||
|     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 :reports, only: [:index, :show, :update] do | ||||
|   | ||||
| @@ -1,6 +1,16 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| 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 | ||||
|     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.' | ||||
|   task confirm_email: :environment do | ||||
|     email = ENV.fetch('USER_EMAIL') | ||||
|     user = User.where(email: email).first | ||||
|     user  = User.find_by(email: email) | ||||
|  | ||||
|     if user | ||||
|       user.update(confirmed_at: Time.now.utc) | ||||
|       puts "User #{email} confirmed." | ||||
|       puts "#{email} confirmed" | ||||
|     else | ||||
|       abort "User #{email} not found." | ||||
|       abort "#{email} not found" | ||||
|     end | ||||
|   end | ||||
|  | ||||
| @@ -32,6 +43,13 @@ namespace :mastodon do | ||||
|     task remove_silenced: :environment do | ||||
|       MediaAttachment.where(account: Account.silenced).find_each(&:destroy) | ||||
|     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 | ||||
|  | ||||
|   namespace :push do | ||||
| @@ -60,7 +78,7 @@ namespace :mastodon do | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     desc 'Clears all timelines so that they would be regenerated on next hit' | ||||
|     desc 'Clears all timelines' | ||||
|     task clear_all: :environment do | ||||
|       Redis.current.keys('feed:*').each { |key| Redis.current.del(key) } | ||||
|     end | ||||
| @@ -126,8 +144,13 @@ namespace :mastodon do | ||||
|       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.avatar.reprocess! | ||||
|         account.header.reprocess! | ||||
|         begin | ||||
|           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 | ||||
|  | ||||
|       Rails.logger.debug 'Done!' | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| { | ||||
|   "name": "mastodon", | ||||
|   "license" : "AGPL-3.0", | ||||
|   "scripts": { | ||||
|     "start": "babel-node ./streaming/index.js --presets es2015,stage-2", | ||||
|     "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.url.to_s).not_to be_empty | ||||
|   end | ||||
|  | ||||
| end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user