Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master

This commit is contained in:
Jenkins 2018-04-14 09:17:23 +00:00
commit e3d0d72b29
8 changed files with 177 additions and 25 deletions

View File

@ -10,6 +10,7 @@ import {
import { defineMessages } from 'react-intl'; 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_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
@ -39,10 +40,13 @@ const unescapeHTML = (html) => {
export function updateNotifications(notification, intlMessages, intlLocale) { export function updateNotifications(notification, intlMessages, intlLocale) {
return (dispatch, getState) => { return (dispatch, getState) => {
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
if (showInColumn) {
dispatch(importFetchedAccount(notification.account)); dispatch(importFetchedAccount(notification.account));
if (notification.status) { if (notification.status) {
dispatch(importFetchedStatus(notification.status)); dispatch(importFetchedStatus(notification.status));
} }
@ -54,6 +58,12 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
}); });
fetchRelatedRelationships(dispatch, [notification]); fetchRelatedRelationships(dispatch, [notification]);
} else if (playSound) {
dispatch({
type: NOTIFICATIONS_UPDATE_NOOP,
meta: { sound: 'boop' },
});
}
// Desktop notifications // Desktop notifications
if (typeof window.Notification !== 'undefined' && showAlert) { 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 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 }); const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
notify.addEventListener('click', () => { notify.addEventListener('click', () => {
window.focus(); window.focus();
notify.close(); notify.close();

View File

@ -114,6 +114,15 @@
], ],
"path": "app/javascript/mastodon/components/domain.json" "path": "app/javascript/mastodon/components/domain.json"
}, },
{
"descriptors": [
{
"defaultMessage": "Load more",
"id": "status.load_more"
}
],
"path": "app/javascript/mastodon/components/load_gap.json"
},
{ {
"descriptors": [ "descriptors": [
{ {

View File

@ -246,6 +246,7 @@
"status.block": "@{name}さんをブロック", "status.block": "@{name}さんをブロック",
"status.cannot_reblog": "この投稿はブーストできません", "status.cannot_reblog": "この投稿はブーストできません",
"status.delete": "削除", "status.delete": "削除",
"status.direct": "@{name}さんにダイレクトメッセージ",
"status.embed": "埋め込み", "status.embed": "埋め込み",
"status.favourite": "お気に入り", "status.favourite": "お気に入り",
"status.load_more": "もっと見る", "status.load_more": "もっと見る",
@ -275,6 +276,7 @@
"tabs_bar.home": "ホーム", "tabs_bar.home": "ホーム",
"tabs_bar.local_timeline": "ローカル", "tabs_bar.local_timeline": "ローカル",
"tabs_bar.notifications": "通知", "tabs_bar.notifications": "通知",
"tabs_bar.search": "検索",
"ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
"upload_area.title": "ドラッグ&ドロップでアップロード", "upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加", "upload_button.label": "メディアを追加",

View File

@ -22,13 +22,15 @@ class ActivityPub::ProcessAccountService < BaseService
create_account if @account.nil? create_account if @account.nil?
update_account update_account
process_tags(@account) process_tags
end end
end end
return if @account.nil?
after_protocol_change! if protocol_changed? after_protocol_change! if protocol_changed?
after_key_change! if key_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 @account
rescue Oj::ParseError rescue Oj::ParseError
@ -189,17 +191,18 @@ class ActivityPub::ProcessAccountService < BaseService
{ redis: Redis.current, key: "process_account:#{@uri}" } { redis: Redis.current, key: "process_account:#{@uri}" }
end end
def process_tags(account) def process_tags
return if @json['tag'].blank? return if @json['tag'].blank?
as_array(@json['tag']).each do |tag| as_array(@json['tag']).each do |tag|
case tag['type'] case tag['type']
when 'Emoji' when 'Emoji'
process_emoji tag, account process_emoji tag
end end
end end
end end
def process_emoji(tag, _account) def process_emoji(tag)
return if skip_download? return if skip_download?
return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank? return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank?

View File

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

View File

@ -3,7 +3,7 @@
class ActivityPub::SynchronizeFeaturedCollectionWorker class ActivityPub::SynchronizeFeaturedCollectionWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: 'pull' sidekiq_options queue: 'pull', unique: :until_executed
def perform(account_id) def perform(account_id)
ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id)) ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id))

View File

@ -4,6 +4,7 @@ ja:
about_hashtag_html: ハッシュタグ <strong>#%{hashtag}</strong> の付いた公開トゥートです。どこでもいいので、連合に参加しているSNS上にアカウントを作れば会話に参加することができます。 about_hashtag_html: ハッシュタグ <strong>#%{hashtag}</strong> の付いた公開トゥートです。どこでもいいので、連合に参加しているSNS上にアカウントを作れば会話に参加することができます。
about_mastodon_html: Mastodon は、オープンなウェブプロトコルを採用した、自由でオープンソースなソーシャルネットワークです。電子メールのような分散型の仕組みを採っています。 about_mastodon_html: Mastodon は、オープンなウェブプロトコルを採用した、自由でオープンソースなソーシャルネットワークです。電子メールのような分散型の仕組みを採っています。
about_this: 詳細情報 about_this: 詳細情報
administered_by: '管理者:'
closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。 closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。
contact: 連絡先 contact: 連絡先
contact_missing: 未設定 contact_missing: 未設定
@ -62,6 +63,13 @@ ja:
are_you_sure: 本当に実行しますか? are_you_sure: 本当に実行しますか?
avatar: アイコン avatar: アイコン
by_domain: ドメイン by_domain: ドメイン
change_email:
changed_msg: メールアドレスの変更に成功しました!
current_email: 現在のメールアドレス
label: メールアドレスを変更
new_email: 新しいメールアドレス
submit: Change Email
title: "%{username} さんのメールアドレスを変更"
confirm: 確認 confirm: 確認
confirmed: 確認済み confirmed: 確認済み
demote: 降格 demote: 降格
@ -71,7 +79,7 @@ ja:
display_name: 表示名 display_name: 表示名
domain: ドメイン domain: ドメイン
edit: 編集 edit: 編集
email: E-mail email: メールアドレス
enable: 有効化 enable: 有効化
enabled: 有効 enabled: 有効
feed_url: フィードURL feed_url: フィードURL
@ -130,6 +138,7 @@ ja:
statuses: トゥート数 statuses: トゥート数
subscribe: 購読する subscribe: 購読する
title: アカウント title: アカウント
unconfirmed_email: 確認待ちのメールアドレス
undo_silenced: サイレンスから戻す undo_silenced: サイレンスから戻す
undo_suspension: 停止から戻す undo_suspension: 停止から戻す
unsubscribe: 購読の解除 unsubscribe: 購読の解除
@ -138,6 +147,7 @@ ja:
action_logs: action_logs:
actions: actions:
assigned_to_self_report: "%{name} さんがレポート %{target} を自身の担当に割り当てました" assigned_to_self_report: "%{name} さんがレポート %{target} を自身の担当に割り当てました"
change_email_user: "%{name} さんが %{target} さんのメールアドレスを変更しました"
confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました" confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました"
create_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を追加しました" create_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を追加しました"
create_domain_block: "%{name} さんがドメイン %{target} をブロックしました" create_domain_block: "%{name} さんがドメイン %{target} をブロックしました"
@ -246,8 +256,8 @@ ja:
title: フィルター title: フィルター
title: 招待 title: 招待
report_notes: report_notes:
created_msg: モデレーションメモを書き込みました! created_msg: レポートメモを書き込みました!
destroyed_msg: モデレーションメモを削除しました! destroyed_msg: レポートメモを削除しました!
reports: reports:
action_taken_by: レポート処理者 action_taken_by: レポート処理者
are_you_sure: 本当に実行しますか? are_you_sure: 本当に実行しますか?
@ -256,15 +266,20 @@ ja:
comment: comment:
label: コメント label: コメント
none: なし none: なし
created_at: レポート日時
delete: 削除 delete: 削除
history: モデレーション履歴
id: ID id: ID
mark_as_resolved: 解決済みとしてマーク mark_as_resolved: 解決済みとしてマーク
mark_as_unresolved: 未解決として再び開く mark_as_unresolved: 未解決として再び開く
notes: notes:
create: 書き込む create: 書き込む
create_and_resolve: 書き込み、解決済みにする create_and_resolve: 書き込み、解決済みにする
create_and_unresolve: 書き込み、未解決として開く
delete: 削除 delete: 削除
label: メモ label: モデレーターメモ
new_label: モデレーターメモの追加
placeholder: このレポートに取られた措置やその他更新を記述してください
nsfw: nsfw:
'false': NSFW オフ 'false': NSFW オフ
'true': NSFW オン 'true': NSFW オン
@ -610,6 +625,10 @@ ja:
missing_resource: リダイレクト先が見つかりませんでした missing_resource: リダイレクト先が見つかりませんでした
proceed: フォローする proceed: フォローする
prompt: 'フォローしようとしています:' prompt: 'フォローしようとしています:'
remote_unfollow:
error: エラー
title: タイトル
unfollowed: フォロー解除しました
sessions: sessions:
activity: 最後のアクティビティ activity: 最後のアクティビティ
browser: ブラウザ browser: ブラウザ
@ -700,6 +719,83 @@ ja:
reblogged: さんがブースト reblogged: さんがブースト
sensitive_content: 閲覧注意 sensitive_content: 閲覧注意
terms: 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} 利用規約・プライバシーポリシー" title: "%{instance} 利用規約・プライバシーポリシー"
themes: themes:
default: Mastodon default: Mastodon

View File

@ -37,5 +37,36 @@ RSpec.describe StatusPin, type: :model do
expect(StatusPin.new(account: account, status: status).save).to be false expect(StatusPin.new(account: account, status: status).save).to be false
end 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
end end