-
- {renderComposePanel &&
}
+ if (place_tab_bar_at_bottom) {
+ return (
+
+
+
+ {renderComposePanel && }
+
+
+
+
+
+
+ {location.pathname !== '/publish' && }
+
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ {renderComposePanel && }
+
+
@@ -168,7 +202,8 @@ export default class ColumnsArea extends ImmutablePureComponent {
- );
+ );
+ }
}
return (
diff --git a/app/javascript/mastodon/features/ui/components/tabs_bar.jsx b/app/javascript/mastodon/features/ui/components/tabs_bar.jsx
new file mode 100644
index 000000000..9b764e465
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/tabs_bar.jsx
@@ -0,0 +1,59 @@
+import PropTypes from 'prop-types';
+import { Component } from 'react';
+import { defineMessages, injectIntl } from 'react-intl';
+import ColumnLink from './column_link';
+import NotificationsCounterIcon from './notifications_counter_icon';
+
+import { ReactComponent as HomeIcon } from '@material-symbols/svg-600/outlined/home-fill.svg';
+import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg';
+import { ReactComponent as PublicIcon } from '@material-symbols/svg-600/outlined/public.svg';
+import { ReactComponent as SearchIcon } from '@material-symbols/svg-600/outlined/search.svg';
+
+const messages = defineMessages({
+ home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
+ notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
+ explore: { id: 'explore.title', defaultMessage: 'Explore' },
+ firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
+ direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
+ favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
+ bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
+ lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
+ preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+ followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
+ about: { id: 'navigation_bar.about', defaultMessage: 'About' },
+ search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
+ gettingStarted: { id: 'getting_started.heading', defaultMessage: 'Getting Started' },
+});
+
+class TabsBar extends Component {
+
+ static contextTypes = {
+ router: PropTypes.object.isRequired,
+ identity: PropTypes.object.isRequired,
+ };
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ };
+
+ isFirehoseActive = (match, location) => {
+ return match || location.pathname.startsWith('/public');
+ };
+
+ render () {
+ const { intl } = this.props;
+
+ return (
+
+
+ } text={intl.formatMessage(messages.notifications)} />
+
+
+
+
+ );
+ }
+
+}
+
+export default injectIntl(TabsBar);
diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx
index 89a8ba560..3141cee72 100644
--- a/app/javascript/mastodon/features/video/index.jsx
+++ b/app/javascript/mastodon/features/video/index.jsx
@@ -133,6 +133,7 @@ class Video extends PureComponent {
autoPlay: PropTypes.bool,
volume: PropTypes.number,
muted: PropTypes.bool,
+ quote: PropTypes.bool,
componentIndex: PropTypes.number,
autoFocus: PropTypes.bool,
};
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
index 596c9ca49..da67620a4 100644
--- a/app/javascript/mastodon/initial_state.js
+++ b/app/javascript/mastodon/initial_state.js
@@ -42,6 +42,7 @@
* @property {boolean} use_blurhash
* @property {boolean=} use_pending_items
* @property {string} version
+ * @property {boolean} place_tab_bar_at_bottom
* @property {string} sso_redirect
*/
@@ -107,6 +108,7 @@ export const languages = initialState?.languages;
export const criticalUpdatesPending = initialState?.critical_updates_pending;
// @ts-expect-error
export const statusPageUrl = getMeta('status_page_url');
+export const place_tab_bar_at_bottom = getMeta('place_tab_bar_at_bottom');
export const sso_redirect = getMeta('sso_redirect');
export default initialState;
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 12d0068d6..8155ca9ad 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -180,6 +180,8 @@
"confirmations.mute.confirm": "Mute",
"confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
+ "confirmations.quote.confirm": "Quote",
+ "confirmations.quote.message": "Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.reply.confirm": "Reply",
@@ -638,6 +640,7 @@
"status.block": "Block @{name}",
"status.bookmark": "Bookmark",
"status.cancel_reblog_private": "Unboost",
+ "status.cannot_quote": "This post cannot be quoted",
"status.cannot_reblog": "This post cannot be boosted",
"status.copy": "Copy link to post",
"status.delete": "Delete",
@@ -662,9 +665,11 @@
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
+ "status.muted_quote": "Muted quote",
"status.open": "Expand this post",
"status.pin": "Pin on profile",
"status.pinned": "Pinned post",
+ "status.quote": "Quote",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",
@@ -688,6 +693,7 @@
"status.translate": "Translate",
"status.translated_from_with": "Translated from {lang} using {provider}",
"status.uncached_media_warning": "Preview not available",
+ "status.unlisted_quote": "Unlisted quote",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index e0cc96d57..36f8b5584 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -23,13 +23,13 @@
"account.cancel_follow_request": "フォローリクエストの取り消し",
"account.copy": "プロフィールへのリンクをコピー",
"account.direct": "@{name}さんに非公開でメンション",
- "account.disable_notifications": "@{name}さんの投稿時の通知を停止",
+ "account.disable_notifications": "@{name}さんのトゥート時の通知を停止",
"account.domain_blocked": "ドメインブロック中",
"account.edit_profile": "プロフィール編集",
- "account.enable_notifications": "@{name}さんの投稿時に通知",
+ "account.enable_notifications": "@{name}さんのトゥート時に通知",
"account.endorse": "プロフィールで紹介する",
- "account.featured_tags.last_status_at": "最終投稿 {date}",
- "account.featured_tags.last_status_never": "投稿がありません",
+ "account.featured_tags.last_status_at": "最終トゥート {date}",
+ "account.featured_tags.last_status_never": "トゥートがありません",
"account.featured_tags.title": "{name}の注目ハッシュタグ",
"account.follow": "フォロー",
"account.follow_back": "フォローバック",
@@ -56,14 +56,14 @@
"account.mutual": "相互フォロー中",
"account.no_bio": "説明が提供されていません。",
"account.open_original_page": "元のページを開く",
- "account.posts": "投稿",
- "account.posts_with_replies": "投稿と返信",
+ "account.posts": "トゥート",
+ "account.posts_with_replies": "トゥートと返信",
"account.report": "@{name}さんを通報",
"account.requested": "フォロー承認待ちです。クリックしてキャンセル",
"account.requested_follow": "{name}さんがあなたにフォローリクエストしました",
"account.share": "@{name}さんのプロフィールを共有する",
"account.show_reblogs": "@{name}さんからのブーストを表示",
- "account.statuses_counter": "{counter} 投稿",
+ "account.statuses_counter": "{counter} トゥート",
"account.unblock": "@{name}さんのブロックを解除",
"account.unblock_domain": "{domain}のブロックを解除",
"account.unblock_short": "ブロック解除",
@@ -121,7 +121,7 @@
"column.lists": "リスト",
"column.mutes": "ミュートしたユーザー",
"column.notifications": "通知",
- "column.pins": "固定された投稿",
+ "column.pins": "固定されたトゥート",
"column.public": "連合タイムライン",
"column_back_button.label": "戻る",
"column_header.hide_settings": "設定を隠す",
@@ -136,21 +136,31 @@
"community.column_settings.remote_only": "リモートのみ表示",
"compose.language.change": "言語を変更",
"compose.language.search": "言語を検索...",
- "compose.published.body": "投稿されました!",
+ "compose.published.body": "トゥートされました!",
"compose.published.open": "開く",
"compose.saved.body": "変更を保存しました。",
"compose_form.direct_message_warning_learn_more": "もっと詳しく",
- "compose_form.encryption_warning": "Mastodonの投稿はエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。",
- "compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
- "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
+ "compose_form.encryption_warning": "Mastodonのトゥートはエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。",
+ "compose_form.hashtag_warning": "このトゥートは公開設定ではないのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。",
+ "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定のトゥートを見ることができます。",
"compose_form.lock_disclaimer.lock": "承認制",
"compose_form.placeholder": "今なにしてる?",
"compose_form.poll.duration": "アンケート期間",
"compose_form.poll.switch_to_multiple": "複数選択に変更",
"compose_form.poll.switch_to_single": "単一選択に変更",
- "compose_form.publish_form": "投稿",
+ "compose_form.publish": "トゥート",
+ "compose_form.publish_form": "トゥート",
+ "compose_form.publish_loud": "{publish}!",
+ "compose_form.save_changes": "変更を保存",
+ "compose_form.sensitive.hide": "メディアを閲覧注意にする",
+ "compose_form.sensitive.marked": "メディアに閲覧注意が設定されています",
+ "compose_form.sensitive.unmarked": "メディアに閲覧注意が設定されていません",
"compose_form.spoiler.marked": "本文は警告の後ろに隠されます",
"compose_form.spoiler.unmarked": "本文は隠されていません",
+ "compose_form.spoiler_placeholder": "ここに警告を書いてください",
+ "compose_form.utilBtns_goji": "誤字盛!",
+ "compose_form.utilBtns_harukin": "はるきん焼却",
+ "compose_form.utilBtns_risa": "りさ姉",
"confirmation_modal.cancel": "キャンセル",
"confirmations.block.block_and_report": "ブロックし通報",
"confirmations.block.confirm": "ブロック",
@@ -170,10 +180,12 @@
"confirmations.logout.confirm": "ログアウト",
"confirmations.logout.message": "本当にログアウトしますか?",
"confirmations.mute.confirm": "ミュート",
- "confirmations.mute.explanation": "これにより相手の投稿と返信は見えなくなりますが、相手はあなたをフォローし続け投稿を見ることができます。",
+ "confirmations.mute.explanation": "これにより相手のトゥートと返信は見えなくなりますが、相手はあなたをフォローし続けトゥートを見ることができます。",
"confirmations.mute.message": "本当に{name}さんをミュートしますか?",
+ "confirmations.quote.confirm": "引用",
+ "confirmations.quote.message": "今引用すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
"confirmations.redraft.confirm": "削除して下書きに戻す",
- "confirmations.redraft.message": "投稿を削除して下書きに戻します。この投稿へのお気に入り登録やブーストは失われ、返信は孤立することになります。よろしいですか?",
+ "confirmations.redraft.message": "トゥートを削除して下書きに戻します。このトゥートへのお気に入り登録やブーストは失われ、返信は孤立することになります。よろしいですか?",
"confirmations.reply.confirm": "返信",
"confirmations.reply.message": "今返信すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
"confirmations.unfollow.confirm": "フォロー解除",
@@ -191,12 +203,12 @@
"directory.recently_active": "最近の活動順",
"disabled_account_banner.account_settings": "アカウント設定",
"disabled_account_banner.text": "あなたのアカウント『{disabledAccount}』は現在無効になっています。",
- "dismissable_banner.community_timeline": "これらは{domain}がホストしている人たちの最新の公開投稿です。",
+ "dismissable_banner.community_timeline": "これらは{domain}がホストしている人たちの最新の公開トゥートです。",
"dismissable_banner.dismiss": "閉じる",
"dismissable_banner.explore_links": "ネットワーク上で話題になっているニュースです。たくさんのユーザーにシェアされた記事ほど上位に表示されます。",
- "dismissable_banner.explore_statuses": "ネットワーク上で注目を集めている投稿です。ブーストやお気に入り登録の多い新しい投稿が上位に表示されます。",
+ "dismissable_banner.explore_statuses": "ネットワーク上で注目を集めているトゥートです。ブーストやお気に入り登録の多い新しいトゥートが上位に表示されます。",
"dismissable_banner.explore_tags": "ネットワーク上でトレンドになっているハッシュタグです。たくさんのユーザーに使われたタグほど上位に表示されます。",
- "dismissable_banner.public_timeline": "{domain}のユーザーがリモートフォローしているアカウントからの公開投稿のタイムラインです。",
+ "dismissable_banner.public_timeline": "{domain}のユーザーがリモートフォローしているアカウントからの公開トゥートのタイムラインです。",
"embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
"embed.preview": "表示例:",
"emoji_button.activity": "活動",
@@ -216,7 +228,7 @@
"emoji_button.travel": "旅行と場所",
"empty_column.account_hides_collections": "このユーザーはこの情報を開示しないことにしています。",
"empty_column.account_suspended": "アカウントは停止されています",
- "empty_column.account_timeline": "投稿がありません!",
+ "empty_column.account_timeline": "トゥートがありません!",
"empty_column.account_unavailable": "プロフィールは利用できません",
"empty_column.blocks": "まだ誰もブロックしていません。",
"empty_column.bookmarked_statuses": "まだ何もブックマーク登録していません。ブックマーク登録するとここに表示されます。",
@@ -224,17 +236,17 @@
"empty_column.direct": "非公開の返信はまだありません。非公開でやりとりをするとここに表示されます。",
"empty_column.domain_blocks": "ブロックしているドメインはありません。",
"empty_column.explore_statuses": "まだ何もありません。後で確認してください。",
- "empty_column.favourited_statuses": "お気に入りの投稿はまだありません。お気に入りに登録すると、ここに表示されます。",
- "empty_column.favourites": "まだ誰もこの投稿をお気に入りに登録していません。お気に入りに登録されると、ここに表示されます。",
+ "empty_column.favourited_statuses": "お気に入りのトゥートはまだありません。お気に入りに登録すると、ここに表示されます。",
+ "empty_column.favourites": "まだ誰もこのトゥートをお気に入りに登録していません。お気に入りに登録されると、ここに表示されます。",
"empty_column.follow_requests": "まだフォローリクエストを受けていません。フォローリクエストを受けるとここに表示されます。",
"empty_column.followed_tags": "まだハッシュタグをフォローしていません。フォローするとここに表示されます。",
"empty_column.hashtag": "このハッシュタグはまだ使われていません。",
"empty_column.home": "ホームタイムラインはまだ空っぽです。だれかをフォローして埋めてみましょう。",
- "empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しい投稿をするとここに表示されます。",
+ "empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しいトゥートをするとここに表示されます。",
"empty_column.lists": "まだリストがありません。リストを作るとここに表示されます。",
"empty_column.mutes": "まだ誰もミュートしていません。",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
- "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう",
+ "empty_column.public": "ここにはまだ何もありません! 公開で何かをトゥートしたり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう",
"error.unexpected_crash.explanation": "不具合かブラウザの互換性問題のため、このページを正しく表示できませんでした。",
"error.unexpected_crash.explanation_addons": "このページは正しく表示できませんでした。このエラーはブラウザのアドオンや自動翻訳ツールによって引き起こされることがあります。",
"error.unexpected_crash.next_steps": "ページの再読み込みをお試しください。それでも解決しない場合、別のブラウザかアプリを使えば使用できることがあります。",
@@ -245,24 +257,24 @@
"explore.suggested_follows": "ユーザー",
"explore.title": "探索する",
"explore.trending_links": "ニュース",
- "explore.trending_statuses": "投稿",
+ "explore.trending_statuses": "トゥート",
"explore.trending_tags": "ハッシュタグ",
- "filter_modal.added.context_mismatch_explanation": "このフィルターカテゴリーはあなたがアクセスした投稿のコンテキストには適用されません。この投稿のコンテキストでもフィルターを適用するにはフィルターを編集する必要があります。",
+ "filter_modal.added.context_mismatch_explanation": "このフィルターカテゴリーはあなたがアクセスしたトゥートのコンテキストには適用されません。このトゥートのコンテキストでもフィルターを適用するにはフィルターを編集する必要があります。",
"filter_modal.added.context_mismatch_title": "コンテキストが一致しません!",
"filter_modal.added.expired_explanation": "このフィルターカテゴリーは有効期限が切れています。適用するには有効期限を更新してください。",
"filter_modal.added.expired_title": "フィルターの有効期限が切れています!",
"filter_modal.added.review_and_configure": "このフィルターカテゴリーを確認して設定するには、{settings_link}に移動します。",
"filter_modal.added.review_and_configure_title": "フィルター設定",
"filter_modal.added.settings_link": "設定",
- "filter_modal.added.short_explanation": "この投稿はフィルターカテゴリー『{title}』に追加されました。",
+ "filter_modal.added.short_explanation": "このトゥートはフィルターカテゴリー『{title}』に追加されました。",
"filter_modal.added.title": "フィルターを追加しました!",
"filter_modal.select_filter.context_mismatch": "このコンテキストには当てはまりません",
"filter_modal.select_filter.expired": "期限切れ",
"filter_modal.select_filter.prompt_new": "新しいカテゴリー: {name}",
"filter_modal.select_filter.search": "検索または新規作成",
"filter_modal.select_filter.subtitle": "既存のカテゴリーを使用するか新規作成します",
- "filter_modal.select_filter.title": "この投稿をフィルターする",
- "filter_modal.title.status": "投稿をフィルターする",
+ "filter_modal.select_filter.title": "このトゥートをフィルターする",
+ "filter_modal.title.status": "トゥートをフィルターする",
"firehose.all": "すべて",
"firehose.local": "このサーバー",
"firehose.remote": "ほかのサーバー",
@@ -289,7 +301,7 @@
"hashtag.column_settings.tag_mode.any": "いずれかを含む",
"hashtag.column_settings.tag_mode.none": "これらを除く",
"hashtag.column_settings.tag_toggle": "このカラムに追加のタグを含める",
- "hashtag.counter_by_accounts": "{count, plural, other {{counter}人投稿}}",
+ "hashtag.counter_by_accounts": "{count, plural, other {{counter}人トゥート}}",
"hashtag.counter_by_uses": "{count, plural, other {{counter}件}}",
"hashtag.counter_by_uses_today": "今日{count, plural, other {{counter}件}}",
"hashtag.follow": "ハッシュタグをフォローする",
@@ -300,17 +312,17 @@
"home.column_settings.basic": "基本設定",
"home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示",
- "home.explore_prompt.body": "ユーザーやハッシュタグをフォローすると、この「ホーム」タイムラインに投稿やブーストが流れるようになります。タイムラインをもう少しにぎやかにしてみませんか?",
+ "home.explore_prompt.body": "ユーザーやハッシュタグをフォローすると、この「ホーム」タイムラインにトゥートやブーストが流れるようになります。タイムラインをもう少しにぎやかにしてみませんか?",
"home.explore_prompt.title": "Mastodonのタイムラインへようこそ。",
"home.hide_announcements": "お知らせを隠す",
"home.pending_critical_update.body": "速やかにMastodonサーバーをアップデートしてください。",
"home.pending_critical_update.link": "詳細",
"home.pending_critical_update.title": "緊急のセキュリティアップデートがあります",
"home.show_announcements": "お知らせを表示",
- "interaction_modal.description.favourite": "Mastodonのアカウントがあれば投稿をお気に入り登録して投稿者に気持ちを伝えたり、あとで見返すことができます。",
- "interaction_modal.description.follow": "Mastodonのアカウントで{name}さんをフォローしてホームフィードで投稿を受け取れます。",
- "interaction_modal.description.reblog": "Mastodonのアカウントでこの投稿をブーストして自分のフォロワーに共有できます。",
- "interaction_modal.description.reply": "Mastodonのアカウントでこの投稿に反応できます。",
+ "interaction_modal.description.favourite": "Mastodonのアカウントがあればトゥートをお気に入り登録してトゥート者に気持ちを伝えたり、あとで見返すことができます。",
+ "interaction_modal.description.follow": "Mastodonのアカウントで{name}さんをフォローしてホームフィードでトゥートを受け取れます。",
+ "interaction_modal.description.reblog": "Mastodonのアカウントでこのトゥートをブーストして自分のフォロワーに共有できます。",
+ "interaction_modal.description.reply": "Mastodonのアカウントでこのトゥートに反応できます。",
"interaction_modal.login.action": "サーバーに移動",
"interaction_modal.login.prompt": "登録したサーバーのドメイン (例: mastodon.social)",
"interaction_modal.no_account_yet": "Mastodonにアカウントがない場合は",
@@ -318,10 +330,10 @@
"interaction_modal.on_this_server": "このサーバー",
"interaction_modal.sign_in": "このサーバーにアカウントがなくても、ほかのサーバーや互換性のあるプラットフォームのアカウントを使用できます。",
"interaction_modal.sign_in_hint": "ワンポイント: ここでは自分のアカウントのドメインを入力します。うまくいかない場合はドメインまで含めた完全なユーザー名を入力してみてください (例: @Mastodon@mastodon.social)。ドメインやユーザー名は登録完了時のメールに記載されています。",
- "interaction_modal.title.favourite": "{name}さんの投稿をお気に入り登録",
+ "interaction_modal.title.favourite": "{name}さんのトゥートをお気に入り登録",
"interaction_modal.title.follow": "{name}さんをフォロー",
- "interaction_modal.title.reblog": "{name}さんの投稿をブースト",
- "interaction_modal.title.reply": "{name}さんの投稿にリプライ",
+ "interaction_modal.title.reblog": "{name}さんのトゥートをブースト",
+ "interaction_modal.title.reply": "{name}さんのトゥートにリプライ",
"intervals.full.days": "{number}日",
"intervals.full.hours": "{number}時間",
"intervals.full.minutes": "{number}分",
@@ -329,12 +341,12 @@
"keyboard_shortcuts.blocked": "ブロックしたユーザーのリストを開く",
"keyboard_shortcuts.boost": "ブースト",
"keyboard_shortcuts.column": "左からn番目のカラムの最新に移動",
- "keyboard_shortcuts.compose": "投稿の入力欄に移動",
+ "keyboard_shortcuts.compose": "トゥートの入力欄に移動",
"keyboard_shortcuts.description": "説明",
"keyboard_shortcuts.direct": "非公開の返信のカラムを開く",
"keyboard_shortcuts.down": "カラム内一つ下に移動",
- "keyboard_shortcuts.enter": "投稿の詳細を表示",
- "keyboard_shortcuts.favourite": "お気に入りの投稿",
+ "keyboard_shortcuts.enter": "トゥートの詳細を表示",
+ "keyboard_shortcuts.favourite": "お気に入りのトゥート",
"keyboard_shortcuts.favourites": "お気に入りリストを開く",
"keyboard_shortcuts.federated": "連合タイムラインを開く",
"keyboard_shortcuts.heading": "キーボードショートカット",
@@ -347,7 +359,7 @@
"keyboard_shortcuts.my_profile": "自分のプロフィールを開く",
"keyboard_shortcuts.notifications": "通知カラムを開く",
"keyboard_shortcuts.open_media": "メディアを開く",
- "keyboard_shortcuts.pinned": "固定した投稿のリストを開く",
+ "keyboard_shortcuts.pinned": "固定したトゥートのリストを開く",
"keyboard_shortcuts.profile": "プロフィールを開く",
"keyboard_shortcuts.reply": "返信",
"keyboard_shortcuts.requests": "フォローリクエストのリストを開く",
@@ -356,8 +368,8 @@
"keyboard_shortcuts.start": "\"スタート\" カラムを開く",
"keyboard_shortcuts.toggle_hidden": "CWで隠れた文を見る/隠す",
"keyboard_shortcuts.toggle_sensitivity": "非表示のメディアを見る/隠す",
- "keyboard_shortcuts.toot": "新規投稿",
- "keyboard_shortcuts.unfocus": "投稿の入力欄・検索欄から離れる",
+ "keyboard_shortcuts.toot": "新規トゥート",
+ "keyboard_shortcuts.unfocus": "トゥートの入力欄・検索欄から離れる",
"keyboard_shortcuts.up": "カラム内一つ上に移動",
"lightbox.close": "閉じる",
"lightbox.compress": "画像ビューボックスを閉じる",
@@ -372,7 +384,7 @@
"lists.delete": "リストを削除",
"lists.edit": "リストを編集",
"lists.edit.submit": "タイトルを変更",
- "lists.exclusive": "ホームタイムラインからこれらの投稿を非表示にする",
+ "lists.exclusive": "ホームタイムラインからこれらのトゥートを非表示にする",
"lists.new.create": "リストを作成",
"lists.new.title_placeholder": "新規リスト名",
"lists.replies_policy.followed": "フォロー中のユーザー全員",
@@ -393,7 +405,7 @@
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.bookmarks": "ブックマーク",
"navigation_bar.community_timeline": "ローカルタイムライン",
- "navigation_bar.compose": "投稿の新規作成",
+ "navigation_bar.compose": "トゥートの新規作成",
"navigation_bar.direct": "非公開の返信",
"navigation_bar.discover": "見つける",
"navigation_bar.domain_blocks": "ブロックしたドメイン",
@@ -408,7 +420,7 @@
"navigation_bar.mutes": "ミュートしたユーザー",
"navigation_bar.opened_in_classic_interface": "投稿やプロフィールを直接開いた場合は一時的に標準UIで表示されます。",
"navigation_bar.personal": "個人用",
- "navigation_bar.pins": "固定した投稿",
+ "navigation_bar.pins": "固定したトゥート",
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.public_timeline": "連合タイムライン",
"navigation_bar.search": "検索",
@@ -416,15 +428,15 @@
"not_signed_in_indicator.not_signed_in": "この機能を使うにはログインする必要があります。",
"notification.admin.report": "{name}さんが{target}さんを通報しました",
"notification.admin.sign_up": "{name}さんがサインアップしました",
- "notification.favourite": "{name}さんがお気に入りしました",
+ "notification.favourite": "{name}さんがあなたのトゥートに╰( ^o^)╮-=ニ=一=三★しました",
"notification.follow": "{name}さんにフォローされました",
"notification.follow_request": "{name}さんがあなたにフォローリクエストしました",
"notification.mention": "{name}さんがあなたに返信しました",
"notification.own_poll": "アンケートが終了しました",
"notification.poll": "アンケートが終了しました",
- "notification.reblog": "{name}さんがあなたの投稿をブーストしました",
- "notification.status": "{name}さんが投稿しました",
- "notification.update": "{name}さんが投稿を編集しました",
+ "notification.reblog": "{name}さんがあなたのトゥートをブーストしました",
+ "notification.status": "{name}さんがトゥートしました",
+ "notification.update": "{name}さんがトゥートを編集しました",
"notifications.clear": "通知を消去",
"notifications.clear_confirmation": "本当に通知を消去しますか?",
"notifications.column_settings.admin.report": "新しい通報:",
@@ -442,7 +454,7 @@
"notifications.column_settings.reblog": "ブースト:",
"notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.sound": "通知音を再生",
- "notifications.column_settings.status": "新しい投稿:",
+ "notifications.column_settings.status": "新しいトゥート:",
"notifications.column_settings.unread_notifications.category": "未読の通知:",
"notifications.column_settings.unread_notifications.highlight": "未読の通知を強調表示",
"notifications.column_settings.update": "編集:",
@@ -491,7 +503,7 @@
"onboarding.steps.follow_people.body": "ユーザーをフォローしてみましょう。これがMastodonを楽しむ基本です。",
"onboarding.steps.follow_people.title": "ホームタイムラインを埋める",
"onboarding.steps.publish_status.body": "試しになにか書いてみましょう。写真、ビデオ、アンケートなど、なんでも大丈夫です {emoji}",
- "onboarding.steps.publish_status.title": "はじめての投稿",
+ "onboarding.steps.publish_status.title": "はじめてのトゥート",
"onboarding.steps.setup_profile.body": "ほかのユーザーが親しみやすいように、プロフィールを整えましょう。",
"onboarding.steps.setup_profile.title": "プロフィールを完成させる",
"onboarding.steps.share_profile.body": "Mastodonのアカウントをほかの人に紹介しましょう。",
@@ -534,7 +546,7 @@
"relative_time.today": "今日",
"reply_indicator.cancel": "キャンセル",
"report.block": "ブロック",
- "report.block_explanation": "相手の投稿が表示されなくなります。相手はあなたの投稿を見ることやフォローすることができません。相手はブロックされていることがわかります。",
+ "report.block_explanation": "相手のトゥートが表示されなくなります。相手はあなたのトゥートを見ることやフォローすることができません。相手はブロックされていることがわかります。",
"report.categories.legal": "法令違反",
"report.categories.other": "その他",
"report.categories.spam": "スパム",
@@ -542,13 +554,13 @@
"report.category.subtitle": "近いものを選択してください",
"report.category.title": "この{type}について教えてください",
"report.category.title_account": "プロフィール",
- "report.category.title_status": "投稿",
+ "report.category.title_status": "トゥート",
"report.close": "完了",
"report.comment.title": "その他に私たちに伝えておくべき事はありますか?",
"report.forward": "{target}に転送する",
"report.forward_hint": "このアカウントは別のサーバーに所属しています。通報内容を匿名で転送しますか?",
"report.mute": "ミュート",
- "report.mute_explanation": "相手の投稿は表示されなくなります。相手は引き続きあなたをフォローして、あなたの投稿を表示することができますが、ミュートされていることはわかりません。",
+ "report.mute_explanation": "相手のトゥートは表示されなくなります。相手は引き続きあなたをフォローして、あなたのトゥートを表示することができますが、ミュートされていることはわかりません。",
"report.next": "次へ",
"report.placeholder": "追加コメント",
"report.reasons.dislike": "興味がありません",
@@ -564,7 +576,7 @@
"report.rules.subtitle": "当てはまるものをすべて選んでください:",
"report.rules.title": "どのルールに違反していますか?",
"report.statuses.subtitle": "当てはまるものをすべて選んでください:",
- "report.statuses.title": "この通報を裏付けるような投稿はありますか?",
+ "report.statuses.title": "この通報を裏付けるようなトゥートはありますか?",
"report.submit": "通報する",
"report.target": "{target}さんを通報する",
"report.thanks.take_action": "次のような方法はいかがでしょうか?",
@@ -572,8 +584,8 @@
"report.thanks.title": "見えないようにしたいですか?",
"report.thanks.title_actionable": "ご報告ありがとうございます、追って確認します。",
"report.unfollow": "@{name}さんのフォローを解除",
- "report.unfollow_explanation": "このアカウントをフォローしています。ホームフィードに彼らの投稿を表示しないようにするには、彼らのフォローを外してください。",
- "report_notification.attached_statuses": "{count, plural, one {{count}件の投稿} other {{count}件の投稿}}が添付されました。",
+ "report.unfollow_explanation": "このアカウントをフォローしています。ホームフィードに彼らのトゥートを表示しないようにするには、彼らのフォローを外してください。",
+ "report_notification.attached_statuses": "{count, plural, one {{count}件のトゥート} other {{count}件のトゥート}}が添付されました。",
"report_notification.categories.legal": "法令違反",
"report_notification.categories.other": "その他",
"report_notification.categories.spam": "スパム",
@@ -585,7 +597,7 @@
"search.quick_action.go_to_account": "プロフィール {x} を見る",
"search.quick_action.go_to_hashtag": "ハッシュタグ {x} を見る",
"search.quick_action.open_url": "MastodonでURLを開く",
- "search.quick_action.status_search": "{x}に該当する投稿",
+ "search.quick_action.status_search": "{x}に該当するトゥート",
"search.search_or_paste": "検索またはURLを入力",
"search_popout.full_text_search_disabled_message": "{domain}では利用できません。",
"search_popout.full_text_search_logged_out_message": "ログイン時のみ利用できます。",
@@ -600,7 +612,7 @@
"search_results.hashtags": "ハッシュタグ",
"search_results.nothing_found": "この検索条件では何も見つかりませんでした",
"search_results.see_all": "すべて表示",
- "search_results.statuses": "投稿",
+ "search_results.statuses": "トゥート",
"search_results.title": "『{q}』の検索結果",
"server_banner.about_active_users": "過去30日間にこのサーバーを使用している人 (月間アクティブユーザー)",
"server_banner.active_users": "人のアクティブユーザー",
@@ -611,40 +623,43 @@
"sign_in_banner.create_account": "アカウント作成",
"sign_in_banner.sign_in": "ログイン",
"sign_in_banner.sso_redirect": "ログインまたは登録",
- "sign_in_banner.text": "アカウントがあればユーザーやハッシュタグをフォローしたり、投稿のお気に入り登録やブースト、投稿への返信ができます。別のサーバーのユーザーとの交流も可能です。",
+ "sign_in_banner.text": "アカウントがあればユーザーやハッシュタグをフォローしたり、トゥートのお気に入り登録やブースト、トゥートへの返信ができます。別のサーバーのユーザーとの交流も可能です。",
"status.admin_account": "@{name}さんのモデレーション画面を開く",
"status.admin_domain": "{domain}のモデレーション画面を開く",
- "status.admin_status": "この投稿をモデレーション画面で開く",
+ "status.admin_status": "このトゥートをモデレーション画面で開く",
"status.block": "@{name}さんをブロック",
"status.bookmark": "ブックマーク",
"status.cancel_reblog_private": "ブースト解除",
- "status.cannot_reblog": "この投稿はブーストできません",
- "status.copy": "投稿へのリンクをコピー",
+ "status.cannot_quote": "このトゥートは引用できません",
+ "status.cannot_reblog": "このトゥートはブーストできません",
+ "status.copy": "トゥートへのリンクをコピー",
"status.delete": "削除",
"status.detailed_status": "詳細な会話ビュー",
- "status.direct": "@{name}さんに非公開で投稿",
+ "status.direct": "@{name}さんに非公開でトゥート",
"status.direct_indicator": "非公開の返信",
"status.edit": "編集",
"status.edited": "{date}に編集",
"status.edited_x_times": "{count}回編集",
"status.embed": "埋め込み",
"status.favourite": "お気に入り",
- "status.filter": "この投稿をフィルターする",
+ "status.filter": "このトゥートをフィルターする",
"status.filtered": "フィルターされました",
- "status.hide": "投稿を非表示",
+ "status.hide": "トゥートを非表示",
"status.history.created": "{name}さんが{date}に作成",
"status.history.edited": "{name}さんが{date}に編集",
"status.load_more": "もっと見る",
"status.media.open": "クリックして開く",
"status.media.show": "クリックして表示",
"status.media_hidden": "非表示のメディア",
- "status.mention": "@{name}さんに投稿",
+ "status.mention": "@{name}さんにトゥート",
"status.more": "もっと見る",
"status.mute": "@{name}さんをミュート",
"status.mute_conversation": "会話をミュート",
+ "status.muted_quote": "ミュートされた引用",
"status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示",
- "status.pinned": "固定された投稿",
+ "status.pinned": "固定されたトゥート",
+ "status.quote": "引用",
"status.read_more": "もっと見る",
"status.reblog": "ブースト",
"status.reblog_private": "ブースト",
@@ -664,13 +679,13 @@
"status.show_more": "もっと見る",
"status.show_more_all": "全て見る",
"status.show_original": "原文を表示",
- "status.title.with_attachments": "{user}さんの投稿 {attachmentCount, plural, other {({attachmentCount}件のメディア)}}",
+ "status.title.with_attachments": "{user}さんのトゥート {attachmentCount, plural, other {({attachmentCount}件のメディア)}}",
"status.translate": "翻訳",
"status.translated_from_with": "{provider}を使って{lang}から翻訳",
"status.uncached_media_warning": "プレビューは使用できません",
"status.unmute_conversation": "会話のミュートを解除",
"status.unpin": "プロフィールへの固定を解除",
- "subscribed_languages.lead": "選択した言語の投稿だけがホームとリストのタイムラインに表示されます。全ての言語の投稿を受け取る場合は全てのチェックを外して下さい。",
+ "subscribed_languages.lead": "選択した言語のトゥートだけがホームとリストのタイムラインに表示されます。全ての言語のトゥートを受け取る場合は全てのチェックを外して下さい。",
"subscribed_languages.save": "変更を保存",
"subscribed_languages.target": "{target}さんの購読言語を変更します",
"tabs_bar.home": "ホーム",
@@ -683,10 +698,10 @@
"timeline_hint.remote_resource_not_displayed": "他のサーバーの{resource}は表示されません。",
"timeline_hint.resources.followers": "フォロワー",
"timeline_hint.resources.follows": "フォロー",
- "timeline_hint.resources.statuses": "以前の投稿",
+ "timeline_hint.resources.statuses": "以前のトゥート",
"trends.counter_by_accounts": "過去{days, plural, one {{days}日} other {{days}日}}に{count, plural, one {{counter}人} other {{counter} 人}}",
"trends.trending_now": "トレンドタグ",
- "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
+ "ui.beforeunload": "Mastodonから離れると送信前のトゥートは失われます。",
"units.short.billion": "{count}B",
"units.short.million": "{count}M",
"units.short.thousand": "{count}K",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index f57bbb77b..9f00e7cc5 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -6,6 +6,8 @@ import {
COMPOSE_CHANGE,
COMPOSE_REPLY,
COMPOSE_REPLY_CANCEL,
+ COMPOSE_QUOTE,
+ COMPOSE_QUOTE_CANCEL,
COMPOSE_DIRECT,
COMPOSE_MENTION,
COMPOSE_SUBMIT_REQUEST,
@@ -67,6 +69,8 @@ const initialState = ImmutableMap({
caretPosition: null,
preselectDate: null,
in_reply_to: null,
+ quote_from: null,
+ quote_from_url: null,
is_composing: false,
is_submitting: false,
is_changing_upload: false,
@@ -119,6 +123,8 @@ function clearAll(state) {
map.set('is_submitting', false);
map.set('is_changing_upload', false);
map.set('in_reply_to', null);
+ map.set('quote_from', null);
+ map.set('quote_from_url', null);
map.set('privacy', state.get('default_privacy'));
map.set('sensitive', state.get('default_sensitive'));
map.set('language', state.get('default_language'));
@@ -250,6 +256,17 @@ const expiresInFromExpiresAt = expires_at => {
return [300, 1800, 3600, 21600, 86400, 259200, 604800].find(expires_in => expires_in >= delta) || 24 * 3600;
};
+const rejectQuoteAltText = html => {
+ const fragment = domParser.parseFromString(html, 'text/html').documentElement;
+
+ const quote_inline = fragment.querySelector('span.quote-inline');
+ if (quote_inline) {
+ quote_inline.remove();
+ }
+
+ return fragment.innerHTML;
+};
+
const mergeLocalHashtagResults = (suggestions, prefix, tagHistory) => {
prefix = prefix.toLowerCase();
if (suggestions.length < 4) {
@@ -335,10 +352,20 @@ export default function compose(state = initialState, action) {
case COMPOSE_COMPOSING_CHANGE:
return state.set('is_composing', action.value);
case COMPOSE_REPLY:
+ case COMPOSE_QUOTE:
return state.withMutations(map => {
map.set('id', null);
- map.set('in_reply_to', action.status.get('id'));
- map.set('text', statusToTextMentions(state, action.status));
+ if (action.type === COMPOSE_REPLY) {
+ map.set('in_reply_to', action.status.get('id'));
+ map.set('quote_from', null);
+ map.set('quote_from_url', null);
+ map.set('text', statusToTextMentions(state, action.status));
+ } else {
+ map.set('in_reply_to', null);
+ map.set('quote_from', action.status.get('id'));
+ map.set('quote_from_url', action.status.get('url'));
+ map.set('text', '');
+ }
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
map.set('focusDate', new Date());
map.set('caretPosition', null);
@@ -370,6 +397,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_UPLOAD_CHANGE_REQUEST:
return state.set('is_changing_upload', true);
case COMPOSE_REPLY_CANCEL:
+ case COMPOSE_QUOTE_CANCEL:
case COMPOSE_RESET:
case COMPOSE_SUBMIT_SUCCESS:
return clearAll(state);
@@ -468,8 +496,10 @@ export default function compose(state = initialState, action) {
}));
case REDRAFT:
return state.withMutations(map => {
- map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
+ map.set('text', action.raw_text || unescapeHTML(rejectQuoteAltText(expandMentions(action.status))));
map.set('in_reply_to', action.status.get('in_reply_to_id'));
+ map.set('quote_from', action.status.getIn(['quote', 'id']));
+ map.set('quote_from_url', action.status.getIn(['quote', 'url']));
map.set('privacy', action.status.get('visibility'));
map.set('media_attachments', action.status.get('media_attachments').map((media) => media.set('unattached', true)));
map.set('focusDate', new Date());
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
index 72835eb91..33a1203fa 100644
--- a/app/javascript/mastodon/reducers/search.js
+++ b/app/javascript/mastodon/reducers/search.js
@@ -3,6 +3,7 @@ import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from '
import {
COMPOSE_MENTION,
COMPOSE_REPLY,
+ COMPOSE_QUOTE,
COMPOSE_DIRECT,
} from '../actions/compose';
import {
@@ -45,6 +46,7 @@ export default function search(state = initialState, action) {
case SEARCH_SHOW:
return state.set('hidden', false);
case COMPOSE_REPLY:
+ case COMPOSE_QUOTE:
case COMPOSE_MENTION:
case COMPOSE_DIRECT:
return state.set('hidden', true);
diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js
index 683fe848f..e3cf12965 100644
--- a/app/javascript/mastodon/reducers/statuses.js
+++ b/app/javascript/mastodon/reducers/statuses.js
@@ -26,6 +26,8 @@ import {
STATUS_TRANSLATE_UNDO,
STATUS_FETCH_REQUEST,
STATUS_FETCH_FAIL,
+ QUOTE_REVEAL,
+ QUOTE_HIDE,
} from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines';
@@ -121,6 +123,14 @@ export default function statuses(state = initialState, action) {
});
case STATUS_COLLAPSE:
return state.setIn([action.id, 'collapsed'], action.isCollapsed);
+ case QUOTE_REVEAL:
+ return state.withMutations(map => {
+ action.ids.forEach(id => map.setIn([id, 'quote_hidden'], false));
+ });
+ case QUOTE_HIDE:
+ return state.withMutations(map => {
+ action.ids.forEach(id => map.setIn([id, 'quote_hidden'], true));
+ });
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
case STATUS_TRANSLATE_SUCCESS:
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index b1c60403e..431585477 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -4,6 +4,7 @@ import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { toServerSideType } from 'mastodon/utils/filters';
import { me } from '../initial_state';
+import {reblogRequest} from '../actions/interactions';
export { makeGetAccount } from "./accounts";
@@ -21,22 +22,54 @@ export const makeGetStatus = () => {
[
(state, { id }) => state.getIn(['statuses', id]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
+ (state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
+ (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])]),
+ (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account'])]),
+ (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', id, 'account'])]),
+ (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
+ (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])]),
+ (state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', id, 'account']), 'moved'])]),
+ (state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account']), 'moved'])]),
+ (state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account']), 'moved'])]),
getFilters,
],
- (statusBase, statusReblog, accountBase, accountReblog, filters) => {
+ (statusBase, statusReblog, statusQuote, accountBase, accountReblog, accountQuote, accountReblogQuote, relationshipBase, relationshipReblog, relationshipQuote, movedBase, movedReblog, movedQuote, filters) => {
if (!statusBase || statusBase.get('isLoading')) {
return null;
}
+ accountBase = accountBase.withMutations(map => {
+ map.set('relationship', relationshipBase);
+ map.set('moved', movedBase);
+ });
+
if (statusReblog) {
+ accountReblog = accountReblog.withMutations(map => {
+ map.set('relationship', relationshipReblog);
+ map.set('moved', movedReblog);
+ });
statusReblog = statusReblog.set('account', accountReblog);
} else {
statusReblog = null;
}
+ if (statusQuote) {
+ accountQuote = accountQuote.withMutations(map => {
+ map.set('relationship', relationshipQuote);
+ map.set('moved', movedQuote);
+ });
+ statusQuote = statusQuote.set('account', accountQuote);
+ } else {
+ statusQuote = null;
+ }
+
+ if (statusReblog && accountReblogQuote) {
+ statusReblog = statusReblog.setIn(['quote', 'account'], accountReblogQuote);
+ }
+
let filtered = false;
if ((accountReblog || accountBase).get('id') !== me && filters) {
let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
@@ -51,6 +84,7 @@ export const makeGetStatus = () => {
return statusBase.withMutations(map => {
map.set('reblog', statusReblog);
+ map.set('quote', statusQuote);
map.set('account', accountBase);
map.set('matched_filters', filtered);
});
diff --git a/app/javascript/mastodon/utils/uniq.js b/app/javascript/mastodon/utils/uniq.js
new file mode 100644
index 000000000..b1bb6f351
--- /dev/null
+++ b/app/javascript/mastodon/utils/uniq.js
@@ -0,0 +1 @@
+export const uniq = (array) => array.filter((x, i, self) => self.indexOf(x) === i);
diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx
index 5edc35537..95a521368 100644
--- a/app/javascript/packs/public.jsx
+++ b/app/javascript/packs/public.jsx
@@ -190,6 +190,29 @@ function loaded() {
const message = (statusEl.dataset.spoiler === 'expanded') ? (localeData['status.show_less'] || 'Show less') : (localeData['status.show_more'] || 'Show more');
spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
});
+
+ delegate(document, '.quote-status', 'click', ({ target }) => {
+ if (target.closest('.status__content__spoiler-link') ||
+ target.closest('.media-gallery') ||
+ target.closest('.video-player') ||
+ target.closest('.audio-player')) {
+ return false;
+ }
+
+ let url = target.closest('.quote-status').getAttribute('dataurl');
+ if (target.closest('.status__display-name')) {
+ url = target.closest('.status__display-name').getAttribute('href');
+ } else if (target.closest('.status-card')) {
+ url = target.closest('.status-card').getAttribute('href');
+ }
+
+ if (window.location.hostname === url.split('/')[2].split(':')[0]) {
+ window.location.href = url;
+ } else {
+ window.open(url, 'blank');
+ }
+ return false;
+ });
}
Rails.delegate(document, '#edit_profile input[type=file]', 'change', ({ target }) => {
diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index 0dd573da9..4ed34ccd6 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -23,3 +23,5 @@
@import 'mastodon/rtl';
@import 'mastodon/accessibility';
@import 'mastodon/rich_text';
+
+@import 'plugin';
\ No newline at end of file
diff --git a/app/javascript/styles/light-pink.scss b/app/javascript/styles/light-pink.scss
new file mode 100644
index 000000000..74eb7d931
--- /dev/null
+++ b/app/javascript/styles/light-pink.scss
@@ -0,0 +1,3 @@
+@import 'light-pink/variables';
+@import 'application';
+@import 'mastodon-light/diff';
\ No newline at end of file
diff --git a/app/javascript/styles/light-pink/variables.scss b/app/javascript/styles/light-pink/variables.scss
new file mode 100644
index 000000000..6591bf965
--- /dev/null
+++ b/app/javascript/styles/light-pink/variables.scss
@@ -0,0 +1,61 @@
+// Dependent colors
+$black: #000000;
+$white: #ffffff;
+
+$classic-base-color: #6e202f;
+$classic-primary-color: #ffa7ae;
+$classic-secondary-color: #faeef1;
+$classic-highlight-color: #ff375b;
+
+$blurple-600: #6e202f; // Iris
+$blurple-500: #ff375b; // Brand purple
+$blurple-300: #ffa7ae; // Faded Blue
+$grey-600: #5a4c4f; // Trout
+$grey-100: #f3dae1; // Topaz
+
+// Differences
+$success-green: lighten(#3c754d, 8%);
+
+$base-overlay-background: $white !default;
+$valid-value-color: $success-green !default;
+
+$ui-base-color: $classic-secondary-color !default;
+$ui-base-lighter-color: #ffe1e9;
+$ui-primary-color: #f1adbf;
+$ui-secondary-color: $classic-base-color !default;
+$ui-highlight-color: $classic-highlight-color !default;
+
+$ui-button-secondary-color: $grey-600 !default;
+$ui-button-secondary-border-color: $grey-600 !default;
+$ui-button-secondary-focus-color: $white !default;
+
+$ui-button-tertiary-color: $blurple-500 !default;
+$ui-button-tertiary-border-color: $blurple-500 !default;
+
+$ui-button-color: $white !default;
+$ui-button-background-color: $blurple-500 !default;
+$ui-button-focus-background-color: $blurple-600 !default;
+
+$primary-text-color: $black !default;
+$darker-text-color: $classic-base-color !default;
+$highlight-text-color: darken($ui-highlight-color, 8%) !default;
+$dark-text-color: #6e202f;
+$action-button-color: #ffa7ae;
+
+$inverted-text-color: $black !default;
+$lighter-text-color: $classic-base-color !default;
+$light-text-color: #ffa7ae;
+
+// Newly added colors
+$account-background-color: $white !default;
+
+// Invert darkened and lightened colors
+@function darken($color, $amount) {
+ @return hsl(hue($color), saturation($color), lightness($color) + $amount);
+}
+
+@function lighten($color, $amount) {
+ @return hsl(hue($color), saturation($color), lightness($color) - $amount);
+}
+
+$emojis-requiring-inversion: 'chains';
\ No newline at end of file
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index f70fa12a5..3a4226af5 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -875,6 +875,29 @@ body > [data-popper-placement] {
background: $ui-highlight-color;
border-color: $ui-highlight-color;
color: $primary-text-color;
+ padding-top: 10px;
+
+ .compose-form__publish-button-wrapper {
+ padding-top: 15px;
+ button {
+ display: inline-block;
+ width: auto;
+
+ margin-right: 0.5em;
+ }
+
+ button:last-child {
+ margin-right: auto;
+ }
+ }
+ }
+
+ .compose-form__utilBtns {
+ padding-top: 10px;
+
+ * {
+ margin-bottom: 0.5em;
+ }
}
}
@@ -935,6 +958,45 @@ body > [data-popper-placement] {
}
}
+.reply-indicator {
+ border-radius: 4px;
+ margin-bottom: 10px;
+ background: $ui-primary-color;
+ padding: 10px;
+ min-height: 23px;
+ overflow-y: auto;
+ flex: 0 2 auto;
+
+ &.quote-indicator {
+ background: $success-green;
+ }
+}
+
+.reply-indicator__header {
+ margin-bottom: 5px;
+ overflow: hidden;
+}
+
+.reply-indicator__cancel {
+ float: right;
+ line-height: 24px;
+}
+
+.reply-indicator__display-name {
+ color: $inverted-text-color;
+ display: block;
+ max-width: 100%;
+ line-height: 24px;
+ overflow: hidden;
+ padding-inline-end: 25px;
+ text-decoration: none;
+}
+
+.reply-indicator__display-avatar {
+ float: left;
+ margin-inline-end: 5px;
+}
+
.status__content--with-action {
cursor: pointer;
}
@@ -1250,6 +1312,10 @@ body > [data-popper-placement] {
.status__content.status__content--collapsed {
max-height: 22px * 15; // 15 lines is roughly above 500 characters
+
+ .quote-status & {
+ max-height: 22px * 5;
+ }
}
.status__content__read-more-button,
@@ -1329,6 +1395,70 @@ body > [data-popper-placement] {
}
}
+.quote-status {
+ border: solid 1px $ui-base-lighter-color;
+ border-radius: 4px !important;
+ padding: 5px !important;
+ margin-top: 8px;
+ position: relative;
+
+ .muted-quote,
+ .unlisted-quote button {
+ color: $dark-text-color;
+ font-size: 15px;
+ width: 100%;
+ border: 0;
+ padding: 0;
+ }
+
+ .muted-quote {
+ text-align: center;
+ cursor: default;
+ }
+
+ .unlisted-quote button {
+ background-color: transparent;
+ cursor: pointer;
+ appearance: none;
+ }
+
+ .status__avatar,
+ .detailed-status__display-avatar {
+ position: absolute;
+ top: 5px !important;
+ left: 5px !important;
+ }
+
+ .display-name {
+ padding-left: 56px;
+ }
+
+ .detailed-status__display-name {
+ margin-bottom: 0;
+ line-height: unset;
+
+ strong,
+ span {
+ display: block;
+ line-height: 22px;
+ }
+ }
+
+ .status__content__text {
+ p {
+ display: inline;
+
+ &::after {
+ content: ' ';
+ }
+ }
+ }
+}
+
+.quote-inline {
+ display: none;
+}
+
.focusable {
&:focus {
outline: 0;
@@ -1344,9 +1474,12 @@ body > [data-popper-placement] {
.status {
padding: 16px;
min-height: 54px;
- border-bottom: 1px solid lighten($ui-base-color, 8%);
cursor: auto;
+ &:not(.quote-status) {
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+ }
+
@keyframes fade {
0% {
opacity: 0;
diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss
index b6d4f98cc..6cd36406a 100644
--- a/app/javascript/styles/mastodon/statuses.scss
+++ b/app/javascript/styles/mastodon/statuses.scss
@@ -40,7 +40,7 @@
&:last-child {
.detailed-status,
- .status,
+ .status:not(.quote-status),
.load-more {
border-bottom: 0;
border-radius: 0 0 4px 4px;
@@ -63,9 +63,18 @@
}
}
+ .detailed-status .quote-status {
+ width: 100%;
+ }
+
+ .quote-status {
+ margin-top: 15px;
+ cursor: pointer;
+ }
+
@media screen and (width <= 740px) {
.detailed-status,
- .status,
+ .status:not(.quote-status),
.load-more {
border-radius: 0 !important;
}
@@ -77,6 +86,10 @@
}
}
+.standalone-timeline .quote-status {
+ cursor: pointer;
+}
+
.button.logo-button svg {
width: 20px;
height: auto;
diff --git a/app/javascript/styles/plugin.scss b/app/javascript/styles/plugin.scss
new file mode 100644
index 000000000..1476fe884
--- /dev/null
+++ b/app/javascript/styles/plugin.scss
@@ -0,0 +1,60 @@
+// ここから下タブバーの実装
+
+//投稿ボタン
+.columns-area__panels__main .button.bottom_right {
+ position: fixed;
+ right: 18px;
+ bottom: 65px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 64px;
+ height: 64px;
+ font-size: 24px;
+ border-radius: 50%;
+}
+
+.tab-ber-bottom .navber {
+ display: none;
+}
+
+.column-link--transparent{
+ justify-content: center;
+}
+
+@media screen and (max-width: 630px) {
+ .tab-ber-bottom .timeline{
+ width:100%;
+ margin: 0 0 50px 0;
+ }
+
+ .tab-ber-bottom .navber{
+ display: flex;
+ width:100%;
+ bottom: 0;
+ position: fixed;
+ }
+
+ .tab-ber-bottom .navber .tabs-bar__wrapper {
+ bottom: 0;
+ width: 100%;
+ align-self: auto;
+ display: flex;
+ border-top: 1px solid lighten($ui-base-color, 8%);
+ }
+
+ .tab-ber-bottom .navber .tabs-bar__wrapper a{
+ flex: 1;
+ text-align: center;
+ }
+
+ .columns-area__panels__pane-tab-ber{
+ display: none;
+ }
+
+ .tab-ber-bottom .navber .tabs-bar__wrapper a span {
+ display: none;
+ }
+}
+
+//ここまで下タブバーの実装
\ No newline at end of file
diff --git a/app/javascript/styles/y-zu-dark b/app/javascript/styles/y-zu-dark
new file mode 160000
index 000000000..a58c699a6
--- /dev/null
+++ b/app/javascript/styles/y-zu-dark
@@ -0,0 +1 @@
+Subproject commit a58c699a605784b3318f54717bfaaa804db4d2f3
diff --git a/app/javascript/styles/y-zu-light b/app/javascript/styles/y-zu-light
new file mode 160000
index 000000000..a5c228314
--- /dev/null
+++ b/app/javascript/styles/y-zu-light
@@ -0,0 +1 @@
+Subproject commit a5c22831445f5669315434f7df49c3c1e1f70d20
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 85195f4c3..b96b1289e 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -78,6 +78,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
@silenced_account_ids = []
@params = {}
+ process_quote
process_status_params
process_tags
process_audience
@@ -130,6 +131,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
media_attachment_ids: attachment_ids,
ordered_media_attachment_ids: attachment_ids,
poll: process_poll,
+ quote: @quote,
}
end
@@ -430,4 +432,20 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
poll.reload
retry
end
+
+ def process_quote
+ return unless (@quote = quote_from_url(@object['quoteUrl']))
+
+ %r{
RE:\s
}.match(@object['content']) do |m|
+ @object['content'] = @object['content'].sub(m[0], "#{m[0].sub(%r{}, '')}")
+ end
+ end
+
+ def quote_from_url(url)
+ return nil if url.nil?
+ quote = ResolveURLService.new.call(url)
+ status_from_uri(quote.uri) if quote
+ rescue
+ nil
+ end
end
diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb
index 581ee835b..0741576c7 100644
--- a/app/lib/text_formatter.rb
+++ b/app/lib/text_formatter.rb
@@ -13,7 +13,8 @@ class TextFormatter
multiline: true,
}.freeze
- attr_reader :text, :options
+ attr_accessor :text
+ attr_reader :options
# @param [String] text
# @param [Hash] options
@@ -21,6 +22,7 @@ class TextFormatter
# @option options [Boolean] :with_domains
# @option options [Boolean] :with_rel_me
# @option options [Array] :preloaded_accounts
+ # @option options [Status] :quote
def initialize(text, options = {})
@text = text
@options = DEFAULT_OPTIONS.merge(options)
@@ -31,7 +33,7 @@ class TextFormatter
end
def to_s
- return ''.html_safe if text.blank?
+ return ''.html_safe if text.blank? & !quote?
html = rewrite do |entity|
if entity[:url]
@@ -43,6 +45,8 @@ class TextFormatter
end
end
+ html += render_quote if quote?
+
html = simple_format(html, {}, sanitize: false).delete("\n") if multiline?
html.html_safe # rubocop:disable Rails/OutputSafety
@@ -126,7 +130,7 @@ class TextFormatter
return "@#{h(entity[:screen_name])}" if account.nil?
- url = ActivityPub::TagManager.instance.url_for(account)
+ url = ap_tag_manager.url_for(account)
display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
<<~HTML.squish
@@ -134,6 +138,13 @@ class TextFormatter
HTML
end
+ def render_quote
+ link = link_to_url({ url: ap_tag_manager.url_for(quote) })
+ <<~HTML.squish
+
RE: #{link}
+ HTML
+ end
+
def entity_cache
@entity_cache ||= EntityCache.instance
end
@@ -142,6 +153,10 @@ class TextFormatter
@tag_manager ||= TagManager.instance
end
+ def ap_tag_manager
+ @ap_tag_manager ||= ActivityPub::TagManager.instance
+ end
+
delegate :local_domain?, to: :tag_manager
def multiline?
@@ -163,4 +178,12 @@ class TextFormatter
def preloaded_accounts?
preloaded_accounts.present?
end
+
+ def quote
+ options[:quote]
+ end
+
+ def quote?
+ quote.present?
+ end
end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index 3312183d4..4223c447a 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -20,4 +20,10 @@ class ApplicationMailer < ActionMailer::Base
headers['X-Auto-Response-Suppress'] = 'All'
headers['Auto-Submitted'] = 'auto-generated'
end
+
+ def set_autoreply_headers!
+ headers['Precedence'] = 'list'
+ headers['X-Auto-Response-Suppress'] = 'All'
+ headers['Auto-Submitted'] = 'auto-generated'
+ end
end
diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb
index bfa8aa2ca..d5ae39294 100644
--- a/app/models/concerns/user/has_settings.rb
+++ b/app/models/concerns/user/has_settings.rb
@@ -95,6 +95,10 @@ module User::HasSettings
settings['web.disable_swiping']
end
+ def setting_place_tab_bar_at_bottom
+ settings['web.place_tab_bar_at_bottom']
+ end
+
def setting_always_send_emails
settings['always_send_emails']
end
diff --git a/app/models/status.rb b/app/models/status.rb
index e3d41cced..1637da292 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -22,6 +22,7 @@
# account_id :bigint(8) not null
# application_id :bigint(8)
# in_reply_to_account_id :bigint(8)
+# quote_id :bigint(8)
# poll_id :bigint(8)
# deleted_at :datetime
# edited_at :datetime
@@ -62,6 +63,7 @@ class Status < ApplicationRecord
with_options class_name: 'Status', optional: true do
belongs_to :thread, foreign_key: 'in_reply_to_id', inverse_of: :replies
belongs_to :reblog, foreign_key: 'reblog_of_id', inverse_of: :reblogs
+ belongs_to :quote, class_name: 'Status', inverse_of: :quoted, optional: true
end
has_many :favourites, inverse_of: :status, dependent: :destroy
@@ -72,6 +74,7 @@ class Status < ApplicationRecord
has_many :mentions, dependent: :destroy, inverse_of: :status
has_many :mentioned_accounts, through: :mentions, source: :account, class_name: 'Account'
has_many :media_attachments, dependent: :nullify
+ has_many :quoted, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quote, dependent: :nullify
# The `dependent` option is enabled by the initial `mentions` association declaration
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status # rubocop:disable Rails/HasManyOrHasOneDependent
@@ -97,6 +100,7 @@ class Status < ApplicationRecord
validates_with DisallowedHashtagsValidator
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
+ validates :quote_visibility, inclusion: { in: %w(public unlisted) }, if: :quote?
accepts_nested_attributes_for :poll
@@ -200,6 +204,14 @@ class Status < ApplicationRecord
!reblog_of_id.nil?
end
+ def quote?
+ !quote_id.nil? && quote
+ end
+
+ def quote_visibility
+ quote&.visibility
+ end
+
def within_realtime_window?
created_at >= REAL_TIME_WINDOW.ago
end
@@ -272,7 +284,12 @@ class Status < ApplicationRecord
fields = [spoiler_text, text]
fields += preloadable_poll.options unless preloadable_poll.nil?
- @emojis = CustomEmoji.from_text(fields.join(' '), account.domain)
+ quote_fields = []
+ quote_fields += [quote.spoiler_text, quote.text] if quote?
+ quote_fields += quote.preloadable_poll.options unless quote&.preloadable_poll.nil?
+
+ @emojis = CustomEmoji.from_text(fields.join(' '), account.domain) +
+ CustomEmoji.from_text(quote_fields.join(' '), quote&.account&.domain)
end
def ordered_media_attachments
diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb
index 030cbec4d..ac053424c 100644
--- a/app/models/user_settings.rb
+++ b/app/models/user_settings.rb
@@ -32,6 +32,7 @@ class UserSettings
setting :expand_content_warnings, default: false
setting :display_media, default: 'default', in: %w(default show_all hide_all)
setting :auto_play, default: false
+ setting :place_tab_bar_at_bottom, default: false
end
namespace :notification_emails do
diff --git a/app/models/webhook.rb b/app/models/webhook.rb
index 304b2b1f1..b28ddea36 100644
--- a/app/models/webhook.rb
+++ b/app/models/webhook.rb
@@ -92,6 +92,14 @@ class Webhook < ApplicationRecord
end
end
+ def validate_permissions
+ errors.add(:events, :invalid_permissions) if defined?(@current_account) && required_permissions.any? { |permission| !@current_account.user_role.can?(permission) }
+ end
+
+ def strip_events
+ self.events = events.filter_map { |str| str.strip.presence } if events.present?
+ end
+
def generate_secret
self.secret = SecureRandom.hex(20) if secret.blank?
end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 27e058199..6b98211b7 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -11,6 +11,9 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
:atom_uri, :in_reply_to_atom_uri,
:conversation
+ attribute :quote_url, if: -> { object.quote? }
+ attribute :misskey_quote, key: :_misskey_quote, if: -> { object.quote? }
+ attribute :misskey_content, key: :_misskey_content, if: -> { object.quote? }
attribute :content
attribute :content_map, if: :language?
attribute :updated, if: :edited?
@@ -138,6 +141,16 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
end
end
+ def quote_url
+ ActivityPub::TagManager.instance.uri_for(object.quote) if object.quote?
+ end
+
+ alias misskey_quote quote_url
+
+ def misskey_content
+ object.text if object.quote?
+ end
+
def local?
object.account.local?
end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 9f7921461..0bf653662 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -29,6 +29,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:use_blurhash] = object_account_user.setting_use_blurhash
store[:use_pending_items] = object_account_user.setting_use_pending_items
store[:show_trends] = Setting.trends && object_account_user.setting_trends
+ store[:place_tab_bar_at_bottom] = object.current_account.user.setting_place_tab_bar_at_bottom
else
store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index fa926cd28..af6c17d3c 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -11,7 +11,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
attributes :domain, :title, :version, :source_url, :description,
:usage, :thumbnail, :languages, :configuration,
- :registrations
+ :registrations, :feature_quote
has_one :contact, serializer: ContactSerializer
has_many :rules, serializer: REST::RuleSerializer
@@ -93,6 +93,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
}
end
+ def feature_quote
+ true
+ end
+
private
def registrations_enabled?
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index d32621541..ce49cb04f 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -18,6 +18,8 @@ class REST::StatusSerializer < ActiveModel::Serializer
attribute :content, unless: :source_requested?
attribute :text, if: :source_requested?
+ attribute :quote_id, if: -> { object.quote? }
+
belongs_to :reblog, serializer: REST::StatusSerializer
belongs_to :application, if: :show_application?
belongs_to :account, serializer: REST::AccountSerializer
@@ -42,6 +44,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
object.in_reply_to_account_id&.to_s
end
+ def quote_id
+ object.quote_id.to_s
+ end
+
def current_user?
!current_user.nil?
end
@@ -196,3 +202,25 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
end
end
+
+class REST::NestedQuoteSerializer < REST::StatusSerializer
+ attribute :quote do
+ nil
+ end
+ attribute :quote_muted, if: :current_user?
+
+ def quote_muted
+ if instance_options && instance_options[:account_relationships]
+ !!instance_options[:account_relationships].muting[object.account_id] ||
+ instance_options[:account_relationships].blocking[object.account_id] ||
+ instance_options[:account_relationships].blocked_by[object.account_id] ||
+ instance_options[:account_relationships].domain_blocking[object.account_id]
+ else
+ current_user.account.muting?(object.account) || object.account.blocking?(current_user.account) || current_user.account.blocking?(object.account) || current_user.account.domain_blocking?(object.account.domain)
+ end
+ end
+end
+
+class REST::StatusSerializer < ActiveModel::Serializer
+ belongs_to :quote, serializer: REST::NestedQuoteSerializer
+end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index c6b600dd7..b18dbddf8 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -75,7 +75,7 @@ class FetchLinkCardService < BaseService
@status.text.scan(URL_PATTERN).map { |array| Addressable::URI.parse(array[1]).normalize }
else
document = Nokogiri::HTML(@status.text)
- links = document.css('a')
+ links = document.css(':not(.quote-inline) > a')
links.filter_map { |a| Addressable::URI.parse(a['href']) unless skip_link?(a) }.filter_map(&:normalize)
end
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index 8aa43ab24..e59b41cf1 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -5,6 +5,7 @@ class PostStatusService < BaseService
include LanguagesHelper
MIN_SCHEDULE_OFFSET = 5.minutes.freeze
+ QUOTE_LINK_PATTERN = /\n\[?(#{FetchLinkCardService::URL_PATTERN})\]?$/
class UnexpectedMentionsError < StandardError
attr_reader :accounts
@@ -30,6 +31,7 @@ class PostStatusService < BaseService
# @option [Doorkeeper::Application] :application
# @option [String] :idempotency Optional idempotency key
# @option [Boolean] :with_rate_limit
+ # @option [Integer] :quote_id
# @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions
# @return [Status]
def call(account, options = {})
@@ -37,11 +39,13 @@ class PostStatusService < BaseService
@options = options
@text = @options[:text] || ''
@in_reply_to = @options[:thread]
+ @quote_id = @options[:quote_id]
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
validate_media!
preprocess_attributes!
+ preprocess_quote!
if scheduled?
schedule_status!
@@ -61,6 +65,19 @@ class PostStatusService < BaseService
private
+ def status_from_uri(uri)
+ ActivityPub::TagManager.instance.uri_to_resource(uri, Status)
+ end
+
+ def quote_from_url(url)
+ return nil if url.nil?
+
+ quote = ResolveURLService.new.call(url)
+ status_from_uri(quote.uri) if quote
+ rescue
+ nil
+ end
+
def preprocess_attributes!
@sensitive = (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
@@ -68,10 +85,21 @@ class PostStatusService < BaseService
@visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced?
@scheduled_at = @options[:scheduled_at]&.to_datetime
@scheduled_at = nil if scheduled_in_the_past?
+
+ if @quote_id.nil? && (link = @text&.match(QUOTE_LINK_PATTERN)) && (@quote_id = quote_from_url(link[1])&.id)
+ @text = @text&.lines(chomp: true)&.[](0...-1)&.join("\n")
+ end
rescue ArgumentError
raise ActiveRecord::RecordInvalid
end
+ def preprocess_quote!
+ if @quote_id.present?
+ quote = Status.find(@quote_id)
+ @quote_id = quote.reblog_of_id.to_s if quote.reblog?
+ end
+ end
+
def process_status!
@status = @account.statuses.new(status_attributes)
process_mentions_service.call(@status, save_records: false)
@@ -193,6 +221,7 @@ class PostStatusService < BaseService
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
application: @options[:application],
rate_limit: @options[:with_rate_limit],
+ quote_id: @quote_id,
}.compact
end
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index dc841ded3..f5e2e3f35 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class StatusLengthValidator < ActiveModel::Validator
- MAX_CHARS = 500
+ MAX_CHARS = 2048
URL_PLACEHOLDER_CHARS = 23
URL_PLACEHOLDER = 'x' * 23
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index c72107367..2db3602dd 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -2,7 +2,7 @@
%html{ lang: I18n.locale }
%head
%meta{ charset: 'utf-8' }/
- %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }/
+ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover'}/
- if cdn_host?
%link{ rel: 'dns-prefetch', href: cdn_host }/
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index 76cd4381d..b49bcf865 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -57,6 +57,7 @@
= ff.input :'web.reduce_motion', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_reduce_motion')
= ff.input :'web.disable_swiping', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_disable_swiping')
= ff.input :'web.use_system_font', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_system_font_ui')
+ = ff.input :'web.place_tab_bar_at_bottom', wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_place_tab_bar_at_bottom')
%h4= t 'appearance.discovery'
diff --git a/app/views/statuses/_author.html.haml b/app/views/statuses/_author.html.haml
new file mode 100644
index 000000000..c700d70a8
--- /dev/null
+++ b/app/views/statuses/_author.html.haml
@@ -0,0 +1,20 @@
+:ruby
+ detailed ||= false
+ inline ||= false
+
+.p-author.h-card
+ = link_to ActivityPub::TagManager.instance.url_for(author), class: "#{detailed ? 'detailed-' : ''}status__display-name u-url", target: stream_link_target, rel: "noopener #{detailed || inline ? '' : 'noreferrer'}" do
+ %div{ class: "#{detailed ? 'detailed-' : ''}status__#{detailed ? 'display-' : ''}avatar" }
+ %div
+ - if prefers_autoplay?
+ = image_tag author.avatar_original_url, alt: '', class: 'u-photo account__avatar'
+ - else
+ = image_tag author.avatar_static_url, alt: '', class: 'u-photo account__avatar'
+ %span.display-name
+ %bdi
+ %strong.display-name__html.p-name.emojify= display_name(author, custom_emojify: true, autoplay: prefers_autoplay?)
+
+ %span.display-name__account
+ = acct(author)
+ - if !inline && author.locked?
+ = fa_icon('lock')
diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml
index c55dff5d9..776c5056e 100644
--- a/app/views/statuses/_detailed_status.html.haml
+++ b/app/views/statuses/_detailed_status.html.haml
@@ -1,40 +1,14 @@
.detailed-status.detailed-status--flex{ class: "detailed-status-#{status.visibility}" }
- .p-author.h-card
- = link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
- .detailed-status__display-avatar
- - if prefers_autoplay?
- = image_tag status.account.avatar_original_url, alt: '', class: 'account__avatar u-photo'
- - else
- = image_tag status.account.avatar_static_url, alt: '', class: 'account__avatar u-photo'
- %span.display-name
- %bdi
- %strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: prefers_autoplay?)
- %span.display-name__account
- = acct(status.account)
- = fa_icon('lock') if status.account.locked?
+ = render 'statuses/author', author: status.account, detailed: true
= account_action_button(status.account)
- .status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- - if status.spoiler_text?
- %p<
- %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
- %button.status__content__spoiler-link= t('statuses.show_more')
- .e-content{ lang: status.language }
- = prerender_custom_emojis(status_content_format(status), status.emojis)
+ = render 'statuses/text', status: status
- - if status.preloadable_poll
- = render_poll_component(status)
+ - if status.quote?
+ = render 'statuses/quote', status: status.quote
- - if !status.ordered_media_attachments.empty?
- - if status.ordered_media_attachments.first.video?
- = render_video_component(status, width: 670, height: 380, detailed: true)
- - elsif status.ordered_media_attachments.first.audio?
- = render_audio_component(status, width: 670, height: 380)
- - else
- = render_media_gallery_component(status, height: 380, standalone: true)
- - elsif status.preview_card
- = render_card_component(status)
+ = render 'statuses/media', status: status, detailed: true
.detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 }
diff --git a/app/views/statuses/_media.html.haml b/app/views/statuses/_media.html.haml
new file mode 100644
index 000000000..e04a58853
--- /dev/null
+++ b/app/views/statuses/_media.html.haml
@@ -0,0 +1,13 @@
+:ruby
+ detailed ||= false
+ quote ||= false
+
+- if !status.ordered_media_attachments.empty?
+ - if status.ordered_media_attachments.first.video?
+ = render_video_component(status, width: 670, height: 380, detailed: detailed)
+ - elsif status.ordered_media_attachments.first.audio?
+ = render_audio_component(status, width: 670, height: 380)
+ - else
+ = render_media_gallery_component(status, height: 380, standalone: true)
+- elsif status.preview_card
+ = render_card_component(status, quote: quote)
diff --git a/app/views/statuses/_quote.html.haml b/app/views/statuses/_quote.html.haml
new file mode 100644
index 000000000..cbbe9cca7
--- /dev/null
+++ b/app/views/statuses/_quote.html.haml
@@ -0,0 +1,6 @@
+.status.quote-status{ dataurl: ActivityPub::TagManager.instance.url_for(status) }
+ = render 'statuses/author', author: status.account, inline: true
+
+ = render 'statuses/text', status: status
+
+ = render 'statuses/media', status: status, quote: true
diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml
index 93effc452..ddc38f48d 100644
--- a/app/views/statuses/_simple_status.html.haml
+++ b/app/views/statuses/_simple_status.html.haml
@@ -35,18 +35,12 @@
.e-content{ lang: status.language }
= prerender_custom_emojis(status_content_format(status), status.emojis)
- - if status.preloadable_poll
- = render_poll_component(status)
+ = render 'statuses/text', status: status
- - if !status.ordered_media_attachments.empty?
- - if status.ordered_media_attachments.first.video?
- = render_video_component(status, width: 610, height: 343)
- - elsif status.ordered_media_attachments.first.audio?
- = render_audio_component(status, width: 610, height: 343)
- - else
- = render_media_gallery_component(status, height: 343)
- - elsif status.preview_card
- = render_card_component(status)
+ - if status.quote?
+ = render 'statuses/quote', status: status.quote
+
+ = render 'statuses/media', status: status
- if !status.in_reply_to_id.nil? && status.in_reply_to_account_id == status.account.id && !hide_show_thread
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do
diff --git a/app/views/statuses/_text.html.haml b/app/views/statuses/_text.html.haml
new file mode 100644
index 000000000..8eb1a2460
--- /dev/null
+++ b/app/views/statuses/_text.html.haml
@@ -0,0 +1,9 @@
+.status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
+ - if status.spoiler_text?
+ %p<
+ %span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
+ %button.status__content__spoiler-link= t('statuses.show_more')
+ .e-content{ lang: status.language }
+ = prerender_custom_emojis(status_content_format(status), status.emojis)
+ - if status.preloadable_poll
+ = render_poll_component(status)
diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml
index 62f2a3eb0..7baecdf7e 100644
--- a/config/locales/doorkeeper.ja.yml
+++ b/config/locales/doorkeeper.ja.yml
@@ -138,7 +138,7 @@ ja:
push: プッシュ通知
reports: 通報
search: 検索
- statuses: 投稿
+ statuses: トゥート
layouts:
admin:
nav:
@@ -178,13 +178,13 @@ ja:
read:notifications: 通知の読み取り
read:reports: 通報の読み取り
read:search: あなたの代わりに検索
- read:statuses: すべての投稿の読み取り
+ read:statuses: すべてのトゥートの読み取り
write: アカウントのすべてのデータの変更
write:accounts: プロフィールの変更
write:blocks: ユーザーのブロックやドメインの非表示
- write:bookmarks: 投稿のブックマーク登録
+ write:bookmarks: トゥートのブックマーク登録
write:conversations: 会話のミュートと削除
- write:favourites: お気に入りの投稿
+ write:favourites: お気に入りのトゥート
write:filters: フィルターの変更
write:follows: あなたの代わりにフォロー、アンフォロー
write:lists: リストの変更
@@ -192,4 +192,4 @@ ja:
write:mutes: アカウントや会話のミュート
write:notifications: 通知の消去
write:reports: 通報の作成
- write:statuses: 投稿の送信
+ write:statuses: トゥートの送信
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 6880f64c5..02bbe5171 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -18,8 +18,8 @@ ja:
pin_errors:
following: おすすめしたい人はあなたが既にフォローしている必要があります
posts:
- other: 投稿
- posts_tab_heading: 投稿
+ other: トゥート
+ posts_tab_heading: トゥート
admin:
account_actions:
action: アクションを実行
@@ -143,7 +143,7 @@ ja:
targeted_reports: このアカウントについての通報
silence: サイレンス
silenced: サイレンス済み
- statuses: 投稿数
+ statuses: トゥート数
strikes: 前回のストライク
subscribe: 購読する
suspend: サスペンド
@@ -192,7 +192,7 @@ ja:
destroy_email_domain_block: メールドメインブロックを削除
destroy_instance: ドメインをブロックする
destroy_ip_block: IPルールを削除
- destroy_status: 投稿を削除
+ destroy_status: トゥートを削除
destroy_unavailable_domain: 配送できないドメインを削除
destroy_user_role: ロールを削除
disable_2fa_user: 二要素認証を無効化
@@ -223,7 +223,7 @@ ja:
update_custom_emoji: カスタム絵文字を更新
update_domain_block: ドメインブロックを更新
update_ip_block: IPルールを更新
- update_status: 投稿を更新
+ update_status: トゥートを更新
update_user_role: ロールを更新
actions:
approve_appeal_html: "%{name}さんが%{target}さんからの抗議を承認しました"
@@ -251,7 +251,7 @@ ja:
destroy_email_domain_block_html: "%{name}さんが%{target}をメールドメインブロックから外しました"
destroy_instance_html: "%{name}さんがドメイン %{target}をブロックしました"
destroy_ip_block_html: "%{name}さんが IP %{target}のルールを削除しました"
- destroy_status_html: "%{name}さんが%{target}さんの投稿を削除しました"
+ destroy_status_html: "%{name}さんが%{target}さんのトゥートを削除しました"
destroy_unavailable_domain_html: "%{name}がドメイン %{target}への配送を再開しました"
destroy_user_role_html: "%{name}さんがロール『%{target}』を削除しました"
disable_2fa_user_html: "%{name}さんが%{target}さんの二要素認証を無効化しました"
@@ -282,7 +282,7 @@ ja:
update_custom_emoji_html: "%{name}さんがカスタム絵文字 %{target}を更新しました"
update_domain_block_html: "%{name}さんが%{target}のドメインブロックを更新しました"
update_ip_block_html: "%{name} さんがIP %{target} のルールを更新しました"
- update_status_html: "%{name}さんが%{target}さんの投稿を更新しました"
+ update_status_html: "%{name}さんが%{target}さんのトゥートを更新しました"
update_user_role_html: "%{name}さんがロール『%{target}』を変更しました"
deleted_account: 削除されたアカウント
empty: ログが見つかりませんでした
@@ -397,7 +397,7 @@ ja:
create: ブロックを作成
hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。
severity:
- desc_html: "制限は、このドメイン上のアカウントからの投稿が、相手をフォローしている場合を除き非表示になります。停止は、このドメイン上のすべてのコンテンツ、メディア、およびプロフィールデータを受け付けなくなります。メディアファイルのみを拒否したい場合はなしを選択します。"
+ desc_html: "制限は、このドメイン上のアカウントからのトゥートが、相手をフォローしている場合を除き非表示になります。停止は、このドメイン上のすべてのコンテンツ、メディア、およびプロフィールデータを受け付けなくなります。メディアファイルのみを拒否したい場合はなしを選択します。"
noop: なし
silence: 制限
suspend: 停止
@@ -494,7 +494,7 @@ ja:
instance_languages_dimension: 人気の言語
instance_media_attachments_measure: 保存されたメディア
instance_reports_measure: 通報
- instance_statuses_measure: 保存された投稿
+ instance_statuses_measure: 保存されたトゥート
delivery:
all: すべて
clear: 配送エラーをクリア
@@ -553,11 +553,11 @@ ja:
relays:
add_new: リレーを追加
delete: 削除
- description_html: "連合リレーとは、登録しているサーバー間の公開投稿を仲介するサーバーです。中小規模のサーバーが連合のコンテンツを見つけるのを助けます。これを使用しない場合、ローカルユーザーがリモートユーザーを手動でフォローする必要があります。"
+ description_html: "連合リレーとは、登録しているサーバー間の公開トゥートを仲介するサーバーです。中小規模のサーバーが連合のコンテンツを見つけるのを助けます。これを使用しない場合、ローカルユーザーがリモートユーザーを手動でフォローする必要があります。"
disable: 無効化
disabled: 無効
enable: 有効化
- enable_hint: 有効にすると、リレーから全ての公開投稿を受信するようになり、またこのサーバーの全ての公開投稿をリレーに送信するようになります。
+ enable_hint: 有効にすると、リレーから全ての公開トゥートを受信するようになり、またこのサーバーの全ての公開トゥートをリレーに送信するようになります。
enabled: 有効
inbox_url: リレーURL
pending: リレーサーバーの承認待ちです
@@ -576,8 +576,8 @@ ja:
action_log: 監査ログ
action_taken_by: 通報処理者
actions:
- delete_description_html: 報告された投稿は削除され、ストライクが記録されます。同じアカウントによる今後の違反行為のエスカレーションに役立てられます。
- mark_as_sensitive_description_html: 報告された投稿のメディアは閲覧注意となり、ストライクが記録され、同じアカウントによる今後の違反行為のエスカレーションに役立てられます。
+ delete_description_html: 報告されたトゥートは削除され、ストライクが記録されます。同じアカウントによる今後の違反行為のエスカレーションに役立てられます。
+ mark_as_sensitive_description_html: 報告されたトゥートのメディアは閲覧注意となり、ストライクが記録され、同じアカウントによる今後の違反行為のエスカレーションに役立てられます。
other_description_html: アカウントの動作を制御するためのオプションや、報告されたアカウントへの通信をカスタマイズするためのオプションを確認してください。
resolve_description_html: 報告されたアカウントに対していかなる措置も取られず、ストライクも記録されず、報告は終了します。
silence_description_html: このアカウントは、すでにフォローしている人、または手動で検索した人にしか見えないため、リーチが極端に制限されます。いつでも元に戻すことができます。このアカウントに対するすべての通報をクローズします。
@@ -598,7 +598,7 @@ ja:
confirm: 確認
confirm_action: "@%{acct} さんに対するアクション"
created_at: 通報日時
- delete_and_resolve: 投稿を削除
+ delete_and_resolve: トゥートを削除
forwarded: 転送済み
forwarded_replies_explanation: これはリモートユーザーによる、リモートコンテンツについての報告です。問題のコンテンツはあなたのサーバー利用者への返信なので、こちらにも転送されて来ました。
forwarded_to: "%{domain}に転送されました"
@@ -626,18 +626,18 @@ ja:
skip_to_actions: アクションに移動
status: ステータス
statuses: 通報内容
- statuses_description_html: 問題の投稿は通報されたアカウントへの連絡時に引用されます
+ statuses_description_html: 問題のトゥートは通報されたアカウントへの連絡時に引用されます
summary:
action_preambles:
- delete_html: "@%{acct}さんの投稿を削除します。この操作は:"
- mark_as_sensitive_html: "@%{acct}さんの投稿を閲覧注意としてマークします。この操作は:"
+ delete_html: "@%{acct}さんのトゥートを削除します。この操作は:"
+ mark_as_sensitive_html: "@%{acct}さんのトゥートを閲覧注意としてマークします。この操作は:"
silence_html: "@%{acct}さんのアカウントを制限 (サイレンス) します。この操作は:"
suspend_html: "@%{acct}さんのアカウントを停止 (サスペンド) します。この操作は:"
actions:
- delete_html: 当該の投稿を削除します
- mark_as_sensitive_html: 当該の投稿に含まれるメディアを閲覧注意にします
+ delete_html: 当該のトゥートを削除します
+ mark_as_sensitive_html: 当該のトゥートに含まれるメディアを閲覧注意にします
silence_html: "@%{acct}さんのプロフィールとコンテンツの表示範囲をフォロー中の人や意図的にプロフィールにアクセスした人のみに制限することで、アカウントを発見されにくくします"
- suspend_html: "@%{acct}さんのアカウントが凍結され、プロフィールとコンテンツへのアクセス、および投稿ができなくなります"
+ suspend_html: "@%{acct}さんのアカウントが凍結され、プロフィールとコンテンツへのアクセス、およびトゥートができなくなります"
close_report: '通報 #%{id} を解決済みにします'
close_reports_html: "@%{acct}さんに対するすべての通報を解決済みにします"
delete_data_html: 停止が解除されないまま30日経過すると、@%{acct}さんのプロフィールとコンテンツは削除されます
@@ -762,7 +762,7 @@ ja:
open: 誰でも登録可
security:
authorized_fetch: 連合サーバーによる署名なしでの情報取得を拒否する
- authorized_fetch_hint: ほかの連合サーバーから受け付けるリクエストに署名を必須にすることで、ユーザーによるブロックおよびドメインブロック両方の効果をより強力にします。ただし連合の処理コストが増えてパフォーマンス面で不利になるほか、このサーバーから送られた反応が届く範囲が狭まったり、連合における互換性の問題を招く可能性もあります。また、この機能は公開投稿やプロフィールへのアクセスをブロックした相手から完全に遮断できるものではありません。
+ authorized_fetch_hint: ほかの連合サーバーから受け付けるリクエストに署名を必須にすることで、ユーザーによるブロックおよびドメインブロック両方の効果をより強力にします。ただし連合の処理コストが増えてパフォーマンス面で不利になるほか、このサーバーから送られた反応が届く範囲が狭まったり、連合における互換性の問題を招く可能性もあります。また、この機能は公開トゥートやプロフィールへのアクセスをブロックした相手から完全に遮断できるものではありません。
authorized_fetch_overridden_hint: この設定は環境変数で指定されているため、ここでは変更できません。
federation_authentication: 連合に署名を必須にする
title: サーバー設定
@@ -798,19 +798,19 @@ ja:
title: メディア
metadata: メタデータ
no_status_selected: 何も選択されていないため、変更されていません
- open: 投稿を開く
- original_status: オリジナルの投稿
+ open: トゥートを開く
+ original_status: オリジナルのトゥート
reblogs: ブースト
- status_changed: 投稿を変更しました
- title: 投稿一覧
+ status_changed: トゥートを変更しました
+ title: トゥート一覧
trending: トレンド
visibility: 公開範囲
with_media: メディアあり
strikes:
actions:
- delete_statuses: "%{name}さんが%{target}さんの投稿を削除しました"
+ delete_statuses: "%{name}さんが%{target}さんのトゥートを削除しました"
disable: "%{name}さんが%{target}さんを凍結しました"
- mark_statuses_as_sensitive: "%{name}さんが%{target}さんの投稿を閲覧注意としてマークしました"
+ mark_statuses_as_sensitive: "%{name}さんが%{target}さんのトゥートを閲覧注意としてマークしました"
none: "%{name}さんが%{target}さんに警告を送信しました"
sensitive: "%{name}さんが%{target}さんのアカウントを閲覧注意としてマークしました"
silence: "%{name}さんが%{target}さんを制限しました"
@@ -889,15 +889,15 @@ ja:
rejected: 拒否
statuses:
allow: 掲載を許可
- allow_account: 投稿者を許可
- description_html: これらは、このサーバーが知っている、たくさんシェアされ、お気に入り登録されている投稿です。新しいユーザーや久しぶりにアクセスするユーザーがフォローする人を探すのに役立ちます。あなたが投稿者を承認し、投稿者が許可するまで、表示されることはありません。また、個別の投稿を許可または拒否することもできます。
+ allow_account: トゥート者を許可
+ description_html: これらは、このサーバーが知っている、たくさんシェアされ、お気に入り登録されているトゥートです。新しいユーザーや久しぶりにアクセスするユーザーがフォローする人を探すのに役立ちます。あなたがトゥート者を承認し、トゥート者が許可するまで、表示されることはありません。また、個別のトゥートを許可または拒否することもできます。
disallow: 掲載を拒否
- disallow_account: 投稿者を拒否
+ disallow_account: トゥート者を拒否
no_status_selected: 何も選択されていないため、変更されていません
- not_discoverable: 投稿者は発見可能であることに同意していません
+ not_discoverable: トゥート者は発見可能であることに同意していません
shared_by:
other: "%{friendly_count}回の共有、お気に入り"
- title: トレンド投稿
+ title: トレンドトゥート
tags:
current_score: 現在のスコア %{score}
dashboard:
@@ -906,7 +906,7 @@ ja:
tag_servers_dimension: 人気のサーバー
tag_servers_measure: その他のサーバー
tag_uses_measure: 合計利用数
- description_html: これらは、多くの投稿に使用されているハッシュタグです。あなたのユーザーが、人々が今一番話題にしていることを知るのに役立ちます。あなたが承認するまで、ハッシュタグは一般に表示されません。
+ description_html: これらは、多くのトゥートに使用されているハッシュタグです。あなたのユーザーが、人々が今一番話題にしていることを知るのに役立ちます。あなたが承認するまで、ハッシュタグは一般に表示されません。
listable: おすすめに表示する
no_tag_selected: 何も選択されていないため、変更されていません
not_listable: おすすめに表示しない
@@ -944,13 +944,13 @@ ja:
new: 新しいwebhook
rotate_secret: シークレットをローテートする
secret: シークレットに署名
- status: 投稿
+ status: トゥート
title: Webhooks
webhook: Webhook
admin_mailer:
new_appeal:
actions:
- delete_statuses: 投稿を削除する
+ delete_statuses: トゥートを削除する
disable: アカウントを無効にする
mark_statuses_as_sensitive: 閲覧注意としてマーク
none: 警告
@@ -978,7 +978,7 @@ ja:
new_trending_links:
title: トレンドリンク
new_trending_statuses:
- title: トレンド投稿
+ title: トレンドトゥート
new_trending_tags:
title: トレンドハッシュタグ
subject: "%{instance}で新しいトレンドが審査待ちです"
@@ -1007,7 +1007,7 @@ ja:
unsubscribe: 購読解除
view: 'リンク:'
view_profile: プロフィールを表示
- view_status: 投稿を表示
+ view_status: トゥートを表示
applications:
created: アプリが作成されました
destroyed: アプリが削除されました
@@ -1132,7 +1132,7 @@ ja:
warning:
before: '続行する前に、次の点を再度確認してください:'
caches: 他のサーバーにコンテンツのキャッシュがずっと残る場合があります
- data_removal: あなたの投稿やその他のデータはこのサーバーから完全に削除されます
+ data_removal: あなたのトゥートやその他のデータはこのサーバーから完全に削除されます
email_change_html: アカウントを削除しなくてもメールアドレスを変更できます
email_contact_html: それでも届かない場合、%{email}までメールで問い合わせてください
email_reconfirmation_html: 確認のメールが届かない場合、もう一度申請できます。
@@ -1156,13 +1156,13 @@ ja:
description_html: これらは、%{instance}のスタッフがあなたのアカウントに対して行った措置や、あなたに送られた警告です。
recipient: 送信元
reject_appeal: 抗議を却下
- status: '投稿 #%{id}'
+ status: 'トゥート #%{id}'
status_removed: 既に削除されています
title: "%{date}に%{action}"
title_actions:
- delete_statuses: 投稿の削除
+ delete_statuses: トゥートの削除
disable: アカウント凍結
- mark_statuses_as_sensitive: 投稿を閲覧注意としてマーク
+ mark_statuses_as_sensitive: トゥートを閲覧注意としてマーク
none: 警告
sensitive: アカウントを閲覧注意としてマーク
silence: アカウントの制限
@@ -1174,7 +1174,7 @@ ja:
invalid_domain: は無効なドメイン名です
edit_profile:
basic_information: 基本情報
- hint_html: "アカウントのトップページや投稿の隣に表示される公開情報です。プロフィールとアイコンを設定することで、ほかのユーザーは親しみやすく、またフォローしやすくなります。"
+ hint_html: "アカウントのトップページやトゥートの隣に表示される公開情報です。プロフィールとアイコンを設定することで、ほかのユーザーは親しみやすく、またフォローしやすくなります。"
other: その他
errors:
'400': 送信されたリクエストは無効であるか、または不正なフォーマットです。
@@ -1198,7 +1198,7 @@ ja:
archive_takeout:
date: 日時
download: ダウンロード
- hint_html: "投稿本文とメディアのアーカイブをリクエストできます。 データはActivityPub形式で、対応しているソフトウェアで読み込むことができます。7日毎にアーカイブをリクエストできます。"
+ hint_html: "トゥート本文とメディアのアーカイブをリクエストできます。 データはActivityPub形式で、対応しているソフトウェアで読み込むことができます。7日毎にアーカイブをリクエストできます。"
in_progress: 準備中...
request: アーカイブをリクエスト
size: 容量
@@ -1213,7 +1213,7 @@ ja:
add_new: 追加
errors:
limit: すでに注目のハッシュタグの上限数に達しています
- hint_html: "注目のハッシュタグとは? プロフィールページに目立つ形で表示され、そのハッシュタグのついたあなたの公開投稿だけを抽出して閲覧できるようにします。クリエイティブな仕事や長期的なプロジェクトを追うのに優れた機能です。"
+ hint_html: "注目のハッシュタグとは? プロフィールページに目立つ形で表示され、そのハッシュタグのついたあなたの公開トゥートだけを抽出して閲覧できるようにします。クリエイティブな仕事や長期的なプロジェクトを追うのに優れた機能です。"
filters:
contexts:
account: プロフィール
@@ -1224,8 +1224,8 @@ ja:
edit:
add_keyword: キーワードを追加
keywords: キーワード
- statuses: 個別の投稿
- statuses_hint_html: このフィルタは、以下のキーワードにマッチするかどうかに関わらず、個々の投稿を選択して適用されます。 フィルターを確認または投稿を削除。
+ statuses: 個別のトゥート
+ statuses_hint_html: このフィルタは、以下のキーワードにマッチするかどうかに関わらず、個々のトゥートを選択して適用されます。 フィルターを確認またはトゥートを削除。
title: フィルターを編集
errors:
deprecated_api_multiple_keywords: これらのパラメータは複数のフィルタキーワードに適用されるため、このアプリケーションから変更できません。 最新のアプリケーションまたはWebインターフェースを使用してください。
@@ -1239,9 +1239,9 @@ ja:
keywords:
other: "%{count}件のキーワード"
statuses:
- other: "%{count}件の投稿"
+ other: "%{count}件のトゥート"
statuses_long:
- other: "%{count}件の投稿を非表示にしました"
+ other: "%{count}件のトゥートを非表示にしました"
title: フィルター
new:
save: 新規フィルターを保存
@@ -1251,8 +1251,8 @@ ja:
batch:
remove: フィルターから削除する
index:
- hint: このフィルターは、他の条件に関係なく個々の投稿を選択する場合に適用されます。Webインターフェースからこのフィルターにさらに投稿を追加できます。
- title: フィルターされた投稿
+ hint: このフィルターは、他の条件に関係なく個々のトゥートを選択する場合に適用されます。Webインターフェースからこのフィルターにさらにトゥートを追加できます。
+ title: フィルターされたトゥート
generic:
all: すべて
all_items_on_page_selected_html:
@@ -1290,14 +1290,14 @@ ja:
overwrite_long: 現在のレコードを新しいもので置き換えます
overwrite_preambles:
blocking_html: "%{filename}の%{total_items}個のアカウントでブロックしたアカウントリストを置き換えます。"
- bookmarks_html: "%{filename}の%{total_items}件の投稿でブックマークの一覧を置き換えます。"
+ bookmarks_html: "%{filename}の%{total_items}件のトゥートでブックマークの一覧を置き換えます。"
domain_blocking_html: "%{filename}の%{total_items}個のドメインで非表示にしたドメインリストを置き換えます。"
following_html: "%{filename}の%{total_items}個のアカウントをフォローします。また、この中に含まれていないアカウントのフォローを解除します。"
lists_html: "作成済みのリストを%{filename}の内容で置き換えます。%{total_items}個のアカウントが新しいリストに追加されます。"
muting_html: "%{filename}の%{total_items}個のアカウントでミュートしたアカウントリストを置き換えます。"
preambles:
blocking_html: "%{filename}の%{total_items}個のアカウントをブロックします。"
- bookmarks_html: "%{filename}の%{total_items}件の投稿をブックマークに追加します。"
+ bookmarks_html: "%{filename}の%{total_items}件のトゥートをブックマークに追加します。"
domain_blocking_html: "%{filename}の%{total_items}個のドメインを非表示にします。"
following_html: "%{filename}の%{total_items}個のアカウントをフォローします。"
lists_html: "%{filename}の%{total_items}個のアカウントをリストに追加します。追加先のリストがない場合は新しく作成されます。"
@@ -1433,7 +1433,7 @@ ja:
sign_up:
subject: "%{name}さんがサインアップしました"
favourite:
- body: "%{name}さんにお気に入り登録された、あなたの投稿があります:"
+ body: "%{name}さんにお気に入り登録された、あなたのトゥートがあります:"
subject: "%{name}さんにお気に入りに登録されました"
title: 新たなお気に入り登録
follow:
@@ -1453,13 +1453,13 @@ ja:
poll:
subject: "%{name} さんの投票が終了しました"
reblog:
- body: "%{name}さんにブーストされた、あなたの投稿があります:"
+ body: "%{name}さんにブーストされた、あなたのトゥートがあります:"
subject: "%{name}さんにブーストされました"
title: 新たなブースト
status:
- subject: "%{name}さんが投稿しました"
+ subject: "%{name}さんがトゥートしました"
update:
- subject: "%{name}さんが投稿を更新しました"
+ subject: "%{name}さんがトゥートを更新しました"
notifications:
administration_emails: 管理にかかわるメール通知
email_events: メールによる通知
@@ -1484,9 +1484,9 @@ ja:
setup: セットアップ
wrong_code: コードが間違っています。サーバーとデバイスの時計にずれがあるかもしれません。
pagination:
- newer: 新しい投稿
+ newer: 新しいトゥート
next: 次
- older: 以前の投稿
+ older: 以前のトゥート
prev: 前
truncate: "…"
polls:
@@ -1503,16 +1503,16 @@ ja:
too_many_options: は%{max}個までです
preferences:
other: その他
- posting_defaults: デフォルトの投稿設定
+ posting_defaults: デフォルトのトゥート設定
public_timelines: 公開タイムライン
privacy:
hint_html: "プロフィールの見えかたや、ほかのユーザーからの見つかりやすさを設定します。Mastodonには自分のアカウントのことをより多くの人に知ってもらうためのさまざまな機能があり、有効・無効をそれぞれ切り換えられます。使いかたや好みに合わせて調節しましょう。"
privacy: プライバシー
- privacy_hint_html: 自分に関する情報をどの程度開示するかについての設定項目です。ユーザーはほかのアカウントのフォロー一覧を見て興味のあるアカウントを探したり、投稿元のアプリ名を見て自分が使いたいアプリの参考にすることがあります。希望に応じて、これらを見られないようにできます。
+ privacy_hint_html: 自分に関する情報をどの程度開示するかについての設定項目です。ユーザーはほかのアカウントのフォロー一覧を見て興味のあるアカウントを探したり、トゥート元のアプリ名を見て自分が使いたいアプリの参考にすることがあります。希望に応じて、これらを見られないようにできます。
reach: つながりやすさ
reach_hint_html: ほかのユーザーからの見つかりやすさと、フォローされる方法についての設定項目です。「エクスプローラー」やおすすめのユーザーに掲載するか、また新しいフォロワーをどのように受け入れるかをここで変更できます。
search: 被検索性
- search_hint_html: 検索での見つかりやすさに関する設定項目です。公開投稿を検索できるようにするかや、Mastodonの外からweb検索でたどり着けるようにするかをここで変更できます。ただし検索エンジンのなかには、この設定に従わずに公開されている情報を利用するものがあるかもしれません。
+ search_hint_html: 検索での見つかりやすさに関する設定項目です。公開トゥートを検索できるようにするかや、Mastodonの外からweb検索でたどり着けるようにするかをここで変更できます。ただし検索エンジンのなかには、この設定に従わずに公開されている情報を利用するものがあるかもしれません。
title: プライバシーとつながりやすさ
privacy_policy:
title: プライバシーポリシー
@@ -1552,11 +1552,11 @@ ja:
rss:
content_warning: '閲覧注意:'
descriptions:
- account: "@%{acct}からの公開投稿"
- tag: "#%{hashtag}が付けられた公開投稿"
+ account: "@%{acct}からの公開トゥート"
+ tag: "#%{hashtag}が付けられた公開トゥート"
scheduled_statuses:
- over_daily_limit: その日予約できる投稿数 %{limit}を超えています
- over_total_limit: 予約できる投稿数 %{limit}を超えています
+ over_daily_limit: その日予約できるトゥート数 %{limit}を超えています
+ over_total_limit: 予約できるトゥート数 %{limit}を超えています
too_soon: より先の時間を指定してください
self_destruct:
lead_html: 残念ながら、%{domain} は恒久的に閉鎖されます。ここにお持ちだったアカウントを今後使うことはできませんが、これまでのデータのバックアップを要求することはまだ可能です。
@@ -1626,7 +1626,7 @@ ja:
preferences: ユーザー設定
profile: プロフィール
relationships: フォロー・フォロワー
- statuses_cleanup: 投稿の自動削除
+ statuses_cleanup: トゥートの自動削除
strikes: モデレーションストライク
two_factor_authentication: 二要素認証
webauthn_authentication: セキュリティキー
@@ -1646,13 +1646,13 @@ ja:
other: '許可されていないハッシュタグが含まれています: %{tags}'
edited_at_html: "%{date} 編集済み"
errors:
- in_reply_not_found: あなたが返信しようとしている投稿は存在しないようです。
+ in_reply_not_found: あなたが返信しようとしているトゥートは存在しないようです。
open_in_web: Webで開く
over_character_limit: 上限は%{max}文字です
pin_errors:
- direct: 返信したユーザーのみに表示される投稿はピン留めできません
- limit: 固定できる投稿数の上限に達しました
- ownership: 他人の投稿を固定することはできません
+ direct: 返信したユーザーのみに表示されるトゥートはピン留めできません
+ limit: 固定できるトゥート数の上限に達しました
+ ownership: 他人のトゥートを固定することはできません
reblog: ブーストを固定することはできません
poll:
total_people:
@@ -1674,26 +1674,26 @@ ja:
unlisted: 非収載
unlisted_long: 誰でも見ることができますが、公開タイムラインには表示されません
statuses_cleanup:
- enabled: 古い投稿を自動的に削除する
- enabled_hint: 設定した期間を過ぎた投稿は、以下の例外に該当しない限り、自動的に削除されます
+ enabled: 古いトゥートを自動的に削除する
+ enabled_hint: 設定した期間を過ぎたトゥートは、以下の例外に該当しない限り、自動的に削除されます
exceptions: 例外
- explanation: 投稿の削除はサーバーに負荷がかかるため、サーバーが混み合っていないときに時間をかけて行われます。
+ explanation: トゥートの削除はサーバーに負荷がかかるため、サーバーが混み合っていないときに時間をかけて行われます。
ignore_favs: 設定しない
ignore_reblogs: 設定しない
interaction_exceptions: インタラクションに基づく例外
- interaction_exceptions_explanation: お気に入りやブーストの基準値を一度超えてしまった投稿は、基準値を下回った後であっても、削除される保証はありません。
+ interaction_exceptions_explanation: お気に入りやブーストの基準値を一度超えてしまったトゥートは、基準値を下回った後であっても、削除される保証はありません。
keep_direct: ダイレクトメッセージを保持
keep_direct_hint: ダイレクトメッセージを削除せずに残します
- keep_media: メディア付きの投稿を保持
- keep_media_hint: メディア付きの投稿を削除せずに残します
- keep_pinned: ピン留めされた投稿を保持
- keep_pinned_hint: ピン留めされた投稿を削除せずに残します
- keep_polls: アンケート付きの投稿を保持
- keep_polls_hint: アンケート付きの投稿を削除せずに残します
- keep_self_bookmark: ブックマークした投稿を保持
- keep_self_bookmark_hint: 自分自身でブックマークした投稿を削除せずに残します
- keep_self_fav: お気に入りに登録した投稿を保持
- keep_self_fav_hint: 自分自身でお気に入りに登録した投稿を削除せずに残します
+ keep_media: メディア付きのトゥートを保持
+ keep_media_hint: メディア付きのトゥートを削除せずに残します
+ keep_pinned: ピン留めされたトゥートを保持
+ keep_pinned_hint: ピン留めされたトゥートを削除せずに残します
+ keep_polls: アンケート付きのトゥートを保持
+ keep_polls_hint: アンケート付きのトゥートを削除せずに残します
+ keep_self_bookmark: ブックマークしたトゥートを保持
+ keep_self_bookmark_hint: 自分自身でブックマークしたトゥートを削除せずに残します
+ keep_self_fav: お気に入りに登録したトゥートを保持
+ keep_self_fav_hint: 自分自身でお気に入りに登録したトゥートを削除せずに残します
min_age:
'1209600': 2週間
'15778476': 半年
@@ -1703,11 +1703,11 @@ ja:
'604800': 1週間
'63113904': 2年
'7889238': 3ヶ月
- min_age_label: 投稿を保持する期間
+ min_age_label: トゥートを保持する期間
min_favs: お気に入りの基準値
- min_favs_hint: この数以上、お気に入り登録された投稿を削除せずに残します。空白にしておくと、お気に入りの数に関わらず投稿を削除します。
+ min_favs_hint: この数以上、お気に入り登録されたトゥートを削除せずに残します。空白にしておくと、お気に入りの数に関わらずトゥートを削除します。
min_reblogs: ブーストの基準値
- min_reblogs_hint: この数以上、ブーストされた投稿を削除せずに残します。空白にしておくと、ブーストされた数に関わらず投稿を削除します。
+ min_reblogs_hint: この数以上、ブーストされたトゥートを削除せずに残します。空白にしておくと、ブーストされた数に関わらずトゥートを削除します。
stream_entries:
sensitive_content: 閲覧注意
strikes:
@@ -1781,26 +1781,26 @@ ja:
spam: スパム
violation: コンテンツは以下のコミュニティガイドラインに違反しています
explanation:
- delete_statuses: あなたの投稿のいくつかは、1つ以上のコミュニティガイドラインに違反していることが判明し、%{instance}のモデレータによって削除されました。
+ delete_statuses: あなたのトゥートのいくつかは、1つ以上のコミュニティガイドラインに違反していることが判明し、%{instance}のモデレータによって削除されました。
disable: アカウントは使用できませんが、プロフィールやその他のデータはそのまま残ります。 データのバックアップをリクエストしたり、アカウント設定を変更したり、アカウントを削除したりできます。
- mark_statuses_as_sensitive: あなたのいくつかの投稿は、%{instance}のモデレータによって閲覧注意としてマークされています。これは、プレビューが表示される前にユーザが投稿内のメディアをタップする必要があることを意味します。あなたは将来投稿する際に自分自身でメディアを閲覧注意としてマークすることができます。
+ mark_statuses_as_sensitive: あなたのいくつかのトゥートは、%{instance}のモデレータによって閲覧注意としてマークされています。これは、プレビューが表示される前にユーザがトゥート内のメディアをタップする必要があることを意味します。あなたは将来トゥートする際に自分自身でメディアを閲覧注意としてマークすることができます。
sensitive: 今後、アップロードされたすべてのメディアファイルは閲覧注意としてマークされ、クリック解除式の警告で覆われるようになります。
- silence: アカウントが制限されています。このサーバーでは既にフォローしている人だけがあなたの投稿を見ることができます。 様々な発見機能から除外されるかもしれません。他の人があなたを手動でフォローすることは可能です。
+ silence: アカウントが制限されています。このサーバーでは既にフォローしている人だけがあなたのトゥートを見ることができます。 様々な発見機能から除外されるかもしれません。他の人があなたを手動でフォローすることは可能です。
suspend: アカウントは使用できなくなり、プロフィールなどのデータにもアクセスできなくなります。約30日後にデータが完全に削除されるまでは、ログインしてデータのバックアップを要求することができますが、アカウントの停止回避を防ぐために一部の基本データを保持します。
reason: '理由:'
- statuses: '投稿:'
+ statuses: 'トゥート:'
subject:
- delete_statuses: "%{acct}さんの投稿が削除されました"
+ delete_statuses: "%{acct}さんのトゥートが削除されました"
disable: あなたのアカウント %{acct}は凍結されました
- mark_statuses_as_sensitive: あなたの%{acct}の投稿は閲覧注意としてマークされました
+ mark_statuses_as_sensitive: あなたの%{acct}のトゥートは閲覧注意としてマークされました
none: "%{acct}に対する警告"
- sensitive: あなたの%{acct}の投稿はこれから閲覧注意としてマークされます
+ sensitive: あなたの%{acct}のトゥートはこれから閲覧注意としてマークされます
silence: あなたのアカウント %{acct}はサイレンスにされました
suspend: あなたのアカウント %{acct}は停止されました
title:
- delete_statuses: 投稿が削除されました
+ delete_statuses: トゥートが削除されました
disable: アカウントが凍結されました
- mark_statuses_as_sensitive: 閲覧注意としてマークされた投稿
+ mark_statuses_as_sensitive: 閲覧注意としてマークされたトゥート
none: 警告
sensitive: 閲覧注意としてマークされたアカウント
silence: アカウントがサイレンスにされました
@@ -1812,7 +1812,7 @@ ja:
新しいフォロワーからフォローリクエストを承認する前に、オプトインで確認できます。
explanation: 始めるにあたってのアドバイスです
final_action: 始めましょう
- final_step: 'さあ、始めましょう! たとえフォロワーがまだいなくても、あなたの公開した投稿はローカルタイムラインやハッシュタグなどを通じて誰かの目にとまるはずです。自己紹介をしたいときには #introductions ハッシュタグが便利かもしれません。'
+ final_step: 'さあ、始めましょう! たとえフォロワーがまだいなくても、あなたの公開したトゥートはローカルタイムラインやハッシュタグなどを通じて誰かの目にとまるはずです。自己紹介をしたいときには #introductions ハッシュタグが便利かもしれません。'
full_handle: あなたの正式なユーザーID
full_handle_hint: 別のサーバーの友達とフォローやメッセージをやり取りする際には、これを伝えることになります。
subject: Mastodonへようこそ
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 0b718c5b6..b40ba3ae2 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -215,7 +215,11 @@ en:
setting_display_media_show_all: Show all
setting_expand_spoilers: Always expand posts marked with content warnings
setting_hide_network: Hide your social graph
+ setting_noindex: Opt-out of search engine indexing
+ setting_place_tab_bar_at_bottom: Place the tab bar at the bottom
setting_reduce_motion: Reduce motion in animations
+ setting_show_application: Disclose application used to send posts
+ setting_show_status_reaction: Show emoji reactions
setting_system_font_ui: Use system's default font
setting_theme: Site theme
setting_trends: Show today's trends
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index 08e866b55..fc25a1efb 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -15,18 +15,18 @@ ja:
account_migration:
acct: 引っ越し先のユーザー名@ドメインを指定してください
account_warning_preset:
- text: URL、ハッシュタグ、メンションなど、投稿に用いる構文が使用できます
+ text: URL、ハッシュタグ、メンションなど、トゥートに用いる構文が使用できます
title: オプションです。受信者には表示されません。
admin_account_action:
- include_statuses: ユーザーは取られた制限や警告の原因となった投稿を確認できるようになります
+ include_statuses: ユーザーは取られた制限や警告の原因となったトゥートを確認できるようになります
send_email_notification: ユーザーは自分のアカウントに何が起こったのか説明を受け取ります
- text_html: オプションです。投稿に用いる構文を使うことができます。簡略化のためプリセット警告文を追加することができます
+ text_html: オプションです。トゥートに用いる構文を使うことができます。簡略化のためプリセット警告文を追加することができます
type_html: "%{acct}さんに対し、何を行うか選択してください"
types:
disable: ユーザーが自分のアカウントを使用できないようにします。コンテンツを削除したり非表示にすることはありません。
none: これを使用すると、他の操作をせずにユーザーに警告を送信できます。
sensitive: このユーザーが添付したメディアを強制的に閲覧注意にする
- silence: ユーザーによる公開投稿を禁止し、フォローしていない人に投稿や通知が表示されないようにします。また、このアカウントに対するすべての通報をクローズします。
+ silence: ユーザーによる公開トゥートを禁止し、フォローしていない人にトゥートや通知が表示されないようにします。また、このアカウントに対するすべての通報をクローズします。
suspend: このアカウントによるすべての活動を禁止し、コンテンツを削除します。この操作は30日以内であれば取り消しが可能です。また、このアカウントに対するすべての通報をクローズします。
warning_preset_id: オプションです。プリセット警告文の末尾に任意の文字列を追加することができます
announcement:
@@ -34,7 +34,7 @@ ja:
ends_at: オプションです。指定すると、お知らせの掲載はその日時で自動的に終了します
scheduled_at: お知らせを今すぐ掲載する場合は空欄にしてください
starts_at: オプションです。お知らせしたい事柄の期間が決まっている場合に使用します
- text: 投稿と同じ構文を使用できます。アナウンスが占める画面のスペースに注意してください
+ text: トゥートと同じ構文を使用できます。アナウンスが占める画面のスペースに注意してください
appeal:
text: 一度だけ異議を申し立てることができます
defaults:
@@ -48,17 +48,20 @@ ja:
email: 確認のメールが送信されます
header: "%{size}までのPNG、GIF、JPGが利用可能です。 %{dimensions}pxまで縮小されます"
inbox_url: 使用したいリレーサーバーのトップページからURLをコピーします
- irreversible: フィルターが後で削除されても、除外された投稿は元に戻せなくなります
+ irreversible: フィルターが後で削除されても、除外されたトゥートは元に戻せなくなります
locale: ユーザーインターフェース、メールやプッシュ通知の言語
password: 少なくとも8文字は入力してください
- phrase: 投稿内容の大文字小文字や閲覧注意に関係なく一致
+ phrase: トゥート内容の大文字小文字や閲覧注意に関係なく一致
scopes: アプリの API に許可するアクセス権を選択してください。最上位のスコープを選択する場合、個々のスコープを選択する必要はありません。
- setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響)
+ setting_aggregate_reblogs: 最近ブーストされたトゥートが新たにブーストされても表示しません (設定後受信したものにのみ影響)
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
setting_display_media_default: 閲覧注意としてマークされたメディアは隠す
setting_display_media_hide_all: メディアを常に隠す
setting_display_media_show_all: メディアを常に表示する
+ setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
+ setting_noindex: 公開プロフィールおよび各トゥートページに影響します
+ setting_show_application: トゥートするのに使用したアプリがトゥートの詳細ビューに表示されるようになります
setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします
username: アルファベット大文字と小文字、数字、アンダーバー「_」が使えます
@@ -71,16 +74,16 @@ ja:
featured_tag:
name: 最近使用したハッシュタグ
filters:
- action: 投稿がフィルタに一致したときに実行するアクションを選択
+ action: トゥートがフィルタに一致したときに実行するアクションを選択
actions:
- hide: フィルタに一致した投稿を完全に非表示にします
- warn: フィルタに一致した投稿を非表示にし、フィルタのタイトルを含む警告を表示します
+ hide: フィルタに一致したトゥートを完全に非表示にします
+ warn: フィルタに一致したトゥートを非表示にし、フィルタのタイトルを含む警告を表示します
form_admin_settings:
- activity_api_enabled: 週単位でローカルで公開された投稿数、アクティブユーザー数、新規登録者数を表示します
+ activity_api_enabled: 週単位でローカルで公開されたトゥート数、アクティブユーザー数、新規登録者数を表示します
backups_retention_period: 生成されたユーザーのアーカイブを指定した日数の間保持します。
bootstrap_timeline_accounts: これらのアカウントは、新しいユーザー向けのおすすめユーザーの一番上にピン留めされます。
closed_registrations_message: アカウント作成を停止している時に表示されます
- content_cache_retention_period: 指定した日数が経過した他のサーバーの投稿とブーストを削除します。削除された投稿は再取得できない場合があります。削除された投稿についたブックマークやお気に入り、ブーストも失われ、元に戻せません。
+ content_cache_retention_period: 指定した日数が経過した他のサーバーのトゥートとブーストを削除します。削除されたトゥートは再取得できない場合があります。削除されたトゥートについたブックマークやお気に入り、ブーストも失われ、元に戻せません。
custom_css: ウェブ版のMastodonでカスタムスタイルを適用できます。
mascot: 上級者向けWebインターフェースのイラストを上書きします。
media_cache_retention_period: 正の値に設定されている場合、ダウンロードされたメディアファイルは指定された日数の後に削除され、リクエストに応じて再ダウンロードされます。
@@ -96,9 +99,9 @@ ja:
status_page_url: 障害発生時などにユーザーがサーバーの状態を確認できるページのURL
theme: ログインしていない人と新規ユーザーに表示されるテーマ。
thumbnail: サーバー情報と共に表示される、アスペクト比が約 2:1 の画像。
- timeline_preview: ログインしていないユーザーがサーバー上の最新の公開投稿を閲覧できるようにします。
+ timeline_preview: ログインしていないユーザーがサーバー上の最新の公開トゥートを閲覧できるようにします。
trendable_by_default: トレンドの審査を省略します。トレンドは掲載後でも個別に除外できます。
- trends: トレンドは、サーバー上で人気を集めている投稿、ハッシュタグ、ニュース記事などが表示されます。
+ trends: トレンドは、サーバー上で人気を集めているトゥート、ハッシュタグ、ニュース記事などが表示されます。
trends_as_landing_page: ログインしていないユーザーに対して、サーバーの説明の代わりにトレンドコンテンツを表示します。トレンドを有効にする必要があります。
form_challenge:
current_password: セキュリティ上重要なエリアにアクセスしています
@@ -126,7 +129,7 @@ ja:
tag:
name: 視認性向上などのためにアルファベット大文字小文字の変更のみ行うことができます
user:
- chosen_languages: 選択すると、選択した言語の投稿のみが公開タイムラインに表示されるようになります
+ chosen_languages: 選択すると、選択した言語のトゥートのみが公開タイムラインに表示されるようになります
role: このロールはユーザーが持つ権限を管理します
user_role:
color: UI 全体でロールの表示に使用される色(16進数RGB形式)
@@ -155,7 +158,7 @@ ja:
text: プリセット警告文
title: タイトル
admin_account_action:
- include_statuses: 通報された投稿をメールに含める
+ include_statuses: 通報されたトゥートをメールに含める
send_email_notification: メールでユーザーに通知
text: カスタム警告文
type: アクション
@@ -204,17 +207,19 @@ ja:
setting_always_send_emails: 常にメール通知を送信する
setting_auto_play_gif: アニメーションGIFを自動再生する
setting_boost_modal: ブーストする前に確認ダイアログを表示する
- setting_default_language: 投稿する言語
- setting_default_privacy: 投稿の公開範囲
+ setting_default_language: トゥートする言語
+ setting_default_privacy: トゥートの公開範囲
setting_default_sensitive: メディアを常に閲覧注意としてマークする
- setting_delete_modal: 投稿を削除する前に確認ダイアログを表示する
+ setting_delete_modal: トゥートを削除する前に確認ダイアログを表示する
setting_disable_swiping: スワイプでの切り替えを無効にする
setting_display_media: メディアの表示
setting_display_media_default: 標準
setting_display_media_hide_all: 非表示
setting_display_media_show_all: 表示
- setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
+ setting_expand_spoilers: 閲覧注意としてマークされたトゥートを常に展開する
setting_hide_network: 繋がりを隠す
+ setting_noindex: 検索エンジンによるインデックスを拒否する
+ setting_place_tab_bar_at_bottom: タブバーを下に配置する
setting_reduce_motion: アニメーションの動きを減らす
setting_system_font_ui: システムのデフォルトフォントを使う
setting_theme: サイトテーマ
@@ -289,7 +294,7 @@ ja:
follow_request: フォローリクエストを受けた時
mention: 返信が来た時
pending_account: 新しいアカウントの承認が必要な時
- reblog: 投稿がブーストされた時
+ reblog: トゥートがブーストされた時
report: 新しい通報が送信された時
software_updates:
all: すべてのアップデートを通知する
@@ -307,7 +312,7 @@ ja:
listable: 検索とディレクトリへの使用を許可する
name: ハッシュタグ
trendable: トレンドへの表示を許可する
- usable: 投稿への使用を許可する
+ usable: トゥートへの使用を許可する
user:
role: ロール
time_zone: タイムゾーン
diff --git a/config/themes.yml b/config/themes.yml
index 9c21c9459..b290f909a 100644
--- a/config/themes.yml
+++ b/config/themes.yml
@@ -1,3 +1,4 @@
default: styles/application.scss
contrast: styles/contrast.scss
mastodon-light: styles/mastodon-light.scss
+light-pink: styles/light-pink.scss
\ No newline at end of file
diff --git a/db/migrate/20180419235016_add_quote_id_to_statuses.rb b/db/migrate/20180419235016_add_quote_id_to_statuses.rb
new file mode 100644
index 000000000..b91c98d86
--- /dev/null
+++ b/db/migrate/20180419235016_add_quote_id_to_statuses.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddQuoteIdToStatuses < ActiveRecord::Migration[5.1]
+ def change
+ add_column :statuses, :quote_id, :bigint, null: true, default: nil
+ end
+end
diff --git a/db/migrate/20200301102028_add_index_to_statuses_quote_id.rb b/db/migrate/20200301102028_add_index_to_statuses_quote_id.rb
new file mode 100644
index 000000000..45293db98
--- /dev/null
+++ b/db/migrate/20200301102028_add_index_to_statuses_quote_id.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddIndexToStatusesQuoteId < ActiveRecord::Migration[5.2]
+ disable_ddl_transaction!
+
+ def change
+ add_index :statuses, :quote_id, algorithm: :concurrently
+ end
+end
diff --git a/db/migrate/20230215074423_move_user_settings.rb b/db/migrate/20230215074423_move_user_settings.rb
index 86231e49e..5476c2b9c 100644
--- a/db/migrate/20230215074423_move_user_settings.rb
+++ b/db/migrate/20230215074423_move_user_settings.rb
@@ -27,6 +27,7 @@ class MoveUserSettings < ActiveRecord::Migration[6.1]
use_blurhash: 'web.use_blurhash',
use_pending_items: 'web.use_pending_items',
crop_images: 'web.crop_images',
+ place_tab_bar_at_bottom: 'web.place_tab_bar_at_bottom',
notification_emails: {
follow: 'notification_emails.follow',
reblog: 'notification_emails.reblog',
diff --git a/db/schema.rb b/db/schema.rb
index 50f4e7189..ff6a675eb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -999,6 +999,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do
t.bigint "account_id", null: false
t.bigint "application_id"
t.bigint "in_reply_to_account_id"
+ t.bigint "quote_id"
t.bigint "poll_id"
t.datetime "deleted_at", precision: nil
t.datetime "edited_at", precision: nil
@@ -1011,6 +1012,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do
t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id", where: "(in_reply_to_account_id IS NOT NULL)"
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", where: "(in_reply_to_id IS NOT NULL)"
+ t.index ["quote_id"], name: "index_statuses_on_quote_id"
t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id"
t.index ["uri"], name: "index_statuses_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)"
end
diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb
index 70efe7c1a..027290b70 100644
--- a/lib/sanitize_ext/sanitize_config.rb
+++ b/lib/sanitize_ext/sanitize_config.rb
@@ -31,6 +31,7 @@ class Sanitize
next true if /^(h|p|u|dt|e)-/.match?(e) # microformats classes
next true if /^(mention|hashtag)$/.match?(e) # semantic classes
next true if /^(ellipsis|invisible)$/.match?(e) # link formatting classes
+ next true if /^quote-inline$/.match?(e) # quote inline classes
end
node['class'] = class_list.join(' ')
diff --git a/public/avatars/original/missing.png b/public/avatars/original/missing.png
index 781370782..fb27b84df 100644
Binary files a/public/avatars/original/missing.png and b/public/avatars/original/missing.png differ
diff --git a/public/favicon.ico b/public/favicon.ico
index b09a98bb9..c18e3bbde 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ