diff options
-rw-r--r-- | app/javascript/mastodon/actions/notifications.js | 37 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/defaultMessages.json | 9 | ||||
-rw-r--r-- | app/javascript/mastodon/locales/ja.json | 2 | ||||
-rw-r--r-- | app/services/activitypub/process_account_service.rb | 13 | ||||
-rw-r--r-- | app/validators/status_pin_validator.rb | 2 | ||||
-rw-r--r-- | app/workers/activitypub/synchronize_featured_collection_worker.rb | 2 | ||||
-rw-r--r-- | config/locales/ja.yml | 104 | ||||
-rw-r--r-- | spec/models/status_pin_spec.rb | 31 |
8 files changed, 176 insertions, 24 deletions
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index da77afbe0..7aa070f56 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -9,7 +9,8 @@ import { } from './importer'; import { defineMessages } from 'react-intl'; -export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; @@ -39,21 +40,30 @@ const unescapeHTML = (html) => { export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { - const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); - const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true); + const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); + const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); - dispatch(importFetchedAccount(notification.account)); - if (notification.status) { - dispatch(importFetchedStatus(notification.status)); - } + if (showInColumn) { + dispatch(importFetchedAccount(notification.account)); - dispatch({ - type: NOTIFICATIONS_UPDATE, - notification, - meta: playSound ? { sound: 'boop' } : undefined, - }); + if (notification.status) { + dispatch(importFetchedStatus(notification.status)); + } + + dispatch({ + type: NOTIFICATIONS_UPDATE, + notification, + meta: playSound ? { sound: 'boop' } : undefined, + }); - fetchRelatedRelationships(dispatch, [notification]); + fetchRelatedRelationships(dispatch, [notification]); + } else if (playSound) { + dispatch({ + type: NOTIFICATIONS_UPDATE_NOOP, + meta: { sound: 'boop' }, + }); + } // Desktop notifications if (typeof window.Notification !== 'undefined' && showAlert) { @@ -61,6 +71,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); + notify.addEventListener('click', () => { window.focus(); notify.close(); diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 6f81db13e..197fe1685 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -121,6 +121,15 @@ "id": "status.load_more" } ], + "path": "app/javascript/mastodon/components/load_gap.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Load more", + "id": "status.load_more" + } + ], "path": "app/javascript/mastodon/components/load_more.json" }, { diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 0efd367e9..205a34e86 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -246,6 +246,7 @@ "status.block": "@{name}さんをブロック", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", + "status.direct": "@{name}さんにダイレクトメッセージ", "status.embed": "埋め込み", "status.favourite": "お気に入り", "status.load_more": "もっと見る", @@ -275,6 +276,7 @@ "tabs_bar.home": "ホーム", "tabs_bar.local_timeline": "ローカル", "tabs_bar.notifications": "通知", + "tabs_bar.search": "検索", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 21c2fc57a..4475a9079 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -22,13 +22,15 @@ class ActivityPub::ProcessAccountService < BaseService create_account if @account.nil? update_account - process_tags(@account) + process_tags end end + return if @account.nil? + after_protocol_change! if protocol_changed? after_key_change! if key_changed? - check_featured_collection! if @account&.featured_collection_url&.present? + check_featured_collection! if @account.featured_collection_url.present? @account rescue Oj::ParseError @@ -189,17 +191,18 @@ class ActivityPub::ProcessAccountService < BaseService { redis: Redis.current, key: "process_account:#{@uri}" } end - def process_tags(account) + def process_tags return if @json['tag'].blank? + as_array(@json['tag']).each do |tag| case tag['type'] when 'Emoji' - process_emoji tag, account + process_emoji tag end end end - def process_emoji(tag, _account) + def process_emoji(tag) return if skip_download? return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank? diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb index 64da04120..2c7bce674 100644 --- a/app/validators/status_pin_validator.rb +++ b/app/validators/status_pin_validator.rb @@ -5,6 +5,6 @@ class StatusPinValidator < ActiveModel::Validator pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) - pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 + pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local? end end diff --git a/app/workers/activitypub/synchronize_featured_collection_worker.rb b/app/workers/activitypub/synchronize_featured_collection_worker.rb index dd676a3ee..7b16d3426 100644 --- a/app/workers/activitypub/synchronize_featured_collection_worker.rb +++ b/app/workers/activitypub/synchronize_featured_collection_worker.rb @@ -3,7 +3,7 @@ class ActivityPub::SynchronizeFeaturedCollectionWorker include Sidekiq::Worker - sidekiq_options queue: 'pull' + sidekiq_options queue: 'pull', unique: :until_executed def perform(account_id) ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id)) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 5f5cb9b01..5f23dd47b 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -4,6 +4,7 @@ ja: about_hashtag_html: ハッシュタグ <strong>#%{hashtag}</strong> の付いた公開トゥートです。どこでもいいので、連合に参加しているSNS上にアカウントを作れば会話に参加することができます。 about_mastodon_html: Mastodon は、オープンなウェブプロトコルを採用した、自由でオープンソースなソーシャルネットワークです。電子メールのような分散型の仕組みを採っています。 about_this: 詳細情報 + administered_by: '管理者:' closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。 contact: 連絡先 contact_missing: 未設定 @@ -62,6 +63,13 @@ ja: are_you_sure: 本当に実行しますか? avatar: アイコン by_domain: ドメイン + change_email: + changed_msg: メールアドレスの変更に成功しました! + current_email: 現在のメールアドレス + label: メールアドレスを変更 + new_email: 新しいメールアドレス + submit: Change Email + title: "%{username} さんのメールアドレスを変更" confirm: 確認 confirmed: 確認済み demote: 降格 @@ -71,7 +79,7 @@ ja: display_name: 表示名 domain: ドメイン edit: 編集 - email: E-mail + email: メールアドレス enable: 有効化 enabled: 有効 feed_url: フィードURL @@ -130,6 +138,7 @@ ja: statuses: トゥート数 subscribe: 購読する title: アカウント + unconfirmed_email: 確認待ちのメールアドレス undo_silenced: サイレンスから戻す undo_suspension: 停止から戻す unsubscribe: 購読の解除 @@ -138,6 +147,7 @@ ja: action_logs: actions: assigned_to_self_report: "%{name} さんがレポート %{target} を自身の担当に割り当てました" + change_email_user: "%{name} さんが %{target} さんのメールアドレスを変更しました" confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました" create_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を追加しました" create_domain_block: "%{name} さんがドメイン %{target} をブロックしました" @@ -246,8 +256,8 @@ ja: title: フィルター title: 招待 report_notes: - created_msg: モデレーションメモを書き込みました! - destroyed_msg: モデレーションメモを削除しました! + created_msg: レポートメモを書き込みました! + destroyed_msg: レポートメモを削除しました! reports: action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? @@ -256,15 +266,20 @@ ja: comment: label: コメント none: なし + created_at: レポート日時 delete: 削除 + history: モデレーション履歴 id: ID mark_as_resolved: 解決済みとしてマーク mark_as_unresolved: 未解決として再び開く notes: create: 書き込む create_and_resolve: 書き込み、解決済みにする + create_and_unresolve: 書き込み、未解決として開く delete: 削除 - label: メモ + label: モデレーターメモ + new_label: モデレーターメモの追加 + placeholder: このレポートに取られた措置やその他更新を記述してください nsfw: 'false': NSFW オフ 'true': NSFW オン @@ -610,6 +625,10 @@ ja: missing_resource: リダイレクト先が見つかりませんでした proceed: フォローする prompt: 'フォローしようとしています:' + remote_unfollow: + error: エラー + title: タイトル + unfollowed: フォロー解除しました sessions: activity: 最後のアクティビティ browser: ブラウザ @@ -700,6 +719,83 @@ ja: reblogged: さんがブースト sensitive_content: 閲覧注意 terms: + body_html: | + <h2>プライバシーポリシー</h2> + <h3 id="collect">どのような情報を収集しますか?</h3> + + <ul> + <li><em>基本的なアカウント情報</em>: 当サイトに登録すると、ユーザー名・メールアドレス・パスワードの入力を求められることがあります。また表示名や自己紹介・プロフィール画像・ヘッダー画像といった追加のプロフィールを登録できます。ユーザー名・表示名・自己紹介・プロフィール画像・ヘッダー画像は常に公開されます。</li> + <li><em>投稿・フォロー・その他公開情報</em>: フォローしているユーザーの一覧は一般公開されます。フォロワーも同様です。メッセージを投稿する際、日時だけでなく投稿に使用したアプリケーション名も記録されます。メッセージには写真や動画といった添付メディアを含むことがあります。「公開」や「未収載」の投稿は一般公開されます。プロフィールに投稿を載せるとそれもまた公開情報となります。投稿はフォロワーに配信されます。場合によっては他のサーバーに配信され、そこにコピーが保存されることを意味します。投稿を削除した場合も同様にフォロワーに配信されます。他の投稿をリブログやお気に入り登録する行動は常に公開されます。</li> + <li><em>「ダイレクト」と「非公開」投稿</em>: すべての投稿はサーバーに保存され、処理されます。「非公開」投稿はフォロワーと投稿に書かれたユーザーに配信されます。「ダイレクト」投稿は投稿に書かれたユーザーにのみ配信されます。場合によっては他のサーバーに配信され、そこにコピーが保存されることを意味します。私たちはこれらの閲覧を一部の許可された者に限定するよう誠意を持って努めます。しかし他のサーバーにおいても同様に扱われるとは限りません。したがって、相手の所属するサーバーを吟味することが重要です。設定で新しいフォロワーの承認または拒否を手動で行うよう切り替えることもできます。<em>サーバー管理者は「ダイレクト」や「非公開」投稿も閲覧する可能性があることを忘れないでください。</em>また受信者がスクリーンショットやコピー、もしくは共有する可能性があることを忘れないでください。<em>いかなる危険な情報もMastodon上で共有しないでください。</em></li> + <li><em>IPアドレスやその他メタデータ</em>: ログインする際IPアドレスだけでなくブラウザーアプリケーション名を記録します。ログインしたセッションはすべてユーザー設定で見直し、取り消すことができます。使用されている最新のIPアドレスは最大12ヵ月間保存されます。またサーバーへのIPアドレスを含むすべてのリクエストのログを保持することがあります。</li> + </ul> + + <hr class="spacer" /> + + <h3 id="use">情報を何に使用しますか?</h3> + + <p>収集した情報は次の用途に使用されることがあります:</p> + + <ul> + <li>Mastodonのコア機能の提供: ログインしている間にかぎり他の人たちと投稿を通じて交流することができます。例えば自分専用のホームタイムラインで投稿をまとめて読むために他の人たちをフォローできます。</li> + <li>コミュニティ維持の補助: 例えばIPアドレスを既知のものと比較し、BAN回避目的の複数登録者やその他違反者を判別します。</li> + <li>提供されたメールアドレスはお知らせの送信・投稿に対するリアクションやメッセージ送信の通知・お問い合わせやその他要求や質問への返信に使用されることがあります。</li> + </ul> + + <hr class="spacer" /> + + <h3 id="protect">情報をどのように保護しますか?</h3> + + <p>私たちはあなたが入力・送信する際や自身の情報にアクセスする際に個人情報を安全に保つため、さまざまなセキュリティ上の対策を実施します。特にブラウザーセッションだけでなくアプリケーションとAPI間の通信もSSLによって保護されます。またパスワードは強力な不可逆アルゴリズムでハッシュ化されます。二段階認証を有効にし、アカウントへのアクセスをさらに安全にすることができます。</p> + + <hr class="spacer" /> + + <h3 id="data-retention">データ保持方針はどうなっていますか?</h3> + + <p>私たちは次のように誠意を持って努めます:</p> + + <ul> + <li>当サイトへのIPアドレスを含むすべての要求に対するサーバーログを90日以内のできるかぎりの間保持します。</li> + <li>登録されたユーザーに関連付けられたIPアドレスを12ヵ月以内の間保持します。</li> + </ul> + + <p>あなたは投稿・添付メディア・プロフィール画像・ヘッダー画像を含む自身のデータのアーカイブを要求し、ダウンロードすることができます。</p> + + <p>あなたはいつでもアカウントの削除を要求できます。削除は取り消すことができません。</p> + + <hr class="spacer"/> + + <h3 id="cookies">クッキーを使用していますか?</h3> + + <p>はい。クッキーは (あなたが許可した場合に) WebサイトやサービスがWebブラウザーを介してコンピューターに保存する小さなファイルです。使用することで Web サイトがブラウザーを識別し、登録済みのアカウントがある場合関連付けます。</p> + + <p>私たちはクッキーを将来の訪問のために設定を保存し呼び出す用途に使用します。</p> + + <hr class="spacer" /> + + <h3 id="disclose">なんらかの情報を外部に提供していますか?</h3> + + <p>私たちは個人を特定できる情報を外部へ販売・取引・その他方法で渡すことはありません。これには当サイトの運営・業務遂行・サービス提供を行ううえで補助する信頼できる第三者をこの機密情報の保護に同意するかぎり含みません。法令の遵守やサイトポリシーの施行、権利・財産・安全の保護に適切と判断した場合、あなたの情報を公開することがあります。</p> + + <p>あなたの公開情報はネットワーク上の他のサーバーにダウンロードされることがあります。相手が異なるサーバーに所属する場合、「公開」と「非公開」投稿はフォロワーの所属するサーバーに配信され、「ダイレクト」投稿は受信者の所属するサーバーに配信されます。</p> + + <p>あなたがアカウントの使用をアプリケーションに許可すると、承認した権限の範囲内で公開プロフィール情報・フォローリスト・フォロワー・リスト・すべての投稿・お気に入り登録にアクセスできます。アプリケーションはメールアドレスやパスワードに決してアクセスできません。</p> + + <hr class="spacer" /> + + <h3 id="coppa">児童オンラインプライバシー保護法の遵守</h3> + + <p>当サイト・製品・サービスは13歳以上の人を対象としています。サーバーが米国にあり、あなたが13歳未満の場合、COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a> - 児童オンラインプライバシー保護法) により当サイトを使用できません。</p> + + <hr class="spacer" /> + + <h3 id="changes">プライバシーポリシーの変更</h3> + + <p>プライバシーポリシーの変更を決定した場合、このページに変更点を掲載します。</p> + + <p>この文章のライセンスはCC-BY-SAです。最終更新日は2018年3月7日です。</p> + + <p>オリジナルの出典: <a href="https://github.com/discourse/discourse">Discourse privacy policy</a></p> title: "%{instance} 利用規約・プライバシーポリシー" themes: default: Mastodon diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb index 6f54f80f9..944baf639 100644 --- a/spec/models/status_pin_spec.rb +++ b/spec/models/status_pin_spec.rb @@ -37,5 +37,36 @@ RSpec.describe StatusPin, type: :model do expect(StatusPin.new(account: account, status: status).save).to be false end + + max_pins = 5 + it 'does not allow pins above the max' do + account = Fabricate(:account) + status = [] + + (max_pins + 1).times do |i| + status[i] = Fabricate(:status, account: account) + end + + max_pins.times do |i| + expect(StatusPin.new(account: account, status: status[i]).save).to be true + end + + expect(StatusPin.new(account: account, status: status[max_pins]).save).to be false + end + + it 'allows pins above the max for remote accounts' do + account = Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') + status = [] + + (max_pins + 1).times do |i| + status[i] = Fabricate(:status, account: account) + end + + max_pins.times do |i| + expect(StatusPin.new(account: account, status: status[i]).save).to be true + end + + expect(StatusPin.new(account: account, status: status[max_pins]).save).to be true + end end end |