diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index da77afbe0e..7aa070f569 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,28 +40,38 @@ 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)); + + if (notification.status) { + dispatch(importFetchedStatus(notification.status)); + } + + dispatch({ + type: NOTIFICATIONS_UPDATE, + notification, + meta: playSound ? { sound: 'boop' } : undefined, + }); + + fetchRelatedRelationships(dispatch, [notification]); + } else if (playSound) { + dispatch({ + type: NOTIFICATIONS_UPDATE_NOOP, + meta: { sound: 'boop' }, + }); } - dispatch({ - type: NOTIFICATIONS_UPDATE, - notification, - meta: playSound ? { sound: 'boop' } : undefined, - }); - - fetchRelatedRelationships(dispatch, [notification]); - // Desktop notifications if (typeof window.Notification !== 'undefined' && showAlert) { const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); 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 6f81db13e1..197fe16859 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -114,6 +114,15 @@ ], "path": "app/javascript/mastodon/components/domain.json" }, + { + "descriptors": [ + { + "defaultMessage": "Load more", + "id": "status.load_more" + } + ], + "path": "app/javascript/mastodon/components/load_gap.json" + }, { "descriptors": [ { diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 0efd367e9d..205a34e866 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 21c2fc57aa..4475a90797 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 64da041209..2c7bce674b 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 dd676a3ee1..7b16d3426a 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 5f5cb9b01f..5f23dd47b2 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -4,6 +4,7 @@ ja: about_hashtag_html: ハッシュタグ #%{hashtag} の付いた公開トゥートです。どこでもいいので、連合に参加している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: | +
収集した情報は次の用途に使用されることがあります:
+ +私たちはあなたが入力・送信する際や自身の情報にアクセスする際に個人情報を安全に保つため、さまざまなセキュリティ上の対策を実施します。特にブラウザーセッションだけでなくアプリケーションとAPI間の通信もSSLによって保護されます。またパスワードは強力な不可逆アルゴリズムでハッシュ化されます。二段階認証を有効にし、アカウントへのアクセスをさらに安全にすることができます。
+ +私たちは次のように誠意を持って努めます:
+ +あなたは投稿・添付メディア・プロフィール画像・ヘッダー画像を含む自身のデータのアーカイブを要求し、ダウンロードすることができます。
+ +あなたはいつでもアカウントの削除を要求できます。削除は取り消すことができません。
+ +はい。クッキーは (あなたが許可した場合に) WebサイトやサービスがWebブラウザーを介してコンピューターに保存する小さなファイルです。使用することで Web サイトがブラウザーを識別し、登録済みのアカウントがある場合関連付けます。
+ +私たちはクッキーを将来の訪問のために設定を保存し呼び出す用途に使用します。
+ +私たちは個人を特定できる情報を外部へ販売・取引・その他方法で渡すことはありません。これには当サイトの運営・業務遂行・サービス提供を行ううえで補助する信頼できる第三者をこの機密情報の保護に同意するかぎり含みません。法令の遵守やサイトポリシーの施行、権利・財産・安全の保護に適切と判断した場合、あなたの情報を公開することがあります。
+ +あなたの公開情報はネットワーク上の他のサーバーにダウンロードされることがあります。相手が異なるサーバーに所属する場合、「公開」と「非公開」投稿はフォロワーの所属するサーバーに配信され、「ダイレクト」投稿は受信者の所属するサーバーに配信されます。
+ +あなたがアカウントの使用をアプリケーションに許可すると、承認した権限の範囲内で公開プロフィール情報・フォローリスト・フォロワー・リスト・すべての投稿・お気に入り登録にアクセスできます。アプリケーションはメールアドレスやパスワードに決してアクセスできません。
+ +当サイト・製品・サービスは13歳以上の人を対象としています。サーバーが米国にあり、あなたが13歳未満の場合、COPPA (Children's Online Privacy Protection Act - 児童オンラインプライバシー保護法) により当サイトを使用できません。
+ +プライバシーポリシーの変更を決定した場合、このページに変更点を掲載します。
+ +この文章のライセンスはCC-BY-SAです。最終更新日は2018年3月7日です。
+ +オリジナルの出典: Discourse privacy policy
title: "%{instance} 利用規約・プライバシーポリシー" themes: default: Mastodon diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb index 6f54f80f9f..944baf6391 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