From b527b6895815d491dce91fbde16601f7f6e57e21 Mon Sep 17 00:00:00 2001 From: KMY Date: Tue, 25 Apr 2023 17:23:04 +0900 Subject: [PATCH 1/6] Add antennas to menu --- .../mastodon/features/compose/components/action_bar.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.jsx index 71208e57f7..ed338c1acb 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/action_bar.jsx @@ -17,6 +17,7 @@ const messages = defineMessages({ domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, + antennas: { id: 'navigation_bar.antennas', defaultMessage: 'Antennas' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, }); @@ -54,6 +55,7 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' }); + menu.push({ text: intl.formatMessage(messages.antennas), href: '/antennas' }); menu.push(null); menu.push({ text: intl.formatMessage(messages.logout), action: this.handleLogout }); From c35e3cb6ac33e23d3b189edddf3bad6a671070fa Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 25 Apr 2023 11:12:37 +0200 Subject: [PATCH 2/6] Add more tips to onboarding flow in web UI (#24646) --- .../mastodon/features/onboarding/follows.jsx | 16 +++-- .../mastodon/features/onboarding/share.jsx | 65 ++++++++++++++++++- .../mastodon/locales/defaultMessages.json | 18 ++++- app/javascript/mastodon/locales/en.json | 6 +- .../styles/mastodon/components.scss | 28 ++++++++ 5 files changed, 125 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/features/onboarding/follows.jsx b/app/javascript/mastodon/features/onboarding/follows.jsx index c42daf2ff1..7cccdefb3d 100644 --- a/app/javascript/mastodon/features/onboarding/follows.jsx +++ b/app/javascript/mastodon/features/onboarding/follows.jsx @@ -46,6 +46,16 @@ class Follows extends React.PureComponent { render () { const { onBack, isLoading, suggestions, account } = this.props; + let loadedContent; + + if (isLoading) { + loadedContent = (new Array(8)).fill().map((_, i) => ); + } else if (suggestions.isEmpty()) { + loadedContent =
; + } else { + loadedContent = suggestions.map(suggestion => ); + } + return ( @@ -59,12 +69,10 @@ class Follows extends React.PureComponent {
- {isLoading ? (new Array(8)).fill().map((_, i) => ) : suggestions.map(suggestion => ( - - ))} + {loadedContent}
-

{text} }} />

+

diff --git a/app/javascript/mastodon/features/onboarding/share.jsx b/app/javascript/mastodon/features/onboarding/share.jsx index 897b2e74d6..9555a3a43a 100644 --- a/app/javascript/mastodon/features/onboarding/share.jsx +++ b/app/javascript/mastodon/features/onboarding/share.jsx @@ -5,14 +5,15 @@ import PropTypes from 'prop-types'; import { me, domain } from 'mastodon/initial_state'; import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl, FormattedMessage, FormattedHTMLMessage } from 'react-intl'; import classNames from 'classnames'; import Icon from 'mastodon/components/icon'; import ArrowSmallRight from './components/arrow_small_right'; import { Link } from 'react-router-dom'; +import SwipeableViews from 'react-swipeable-views'; const messages = defineMessages({ - shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on Mastodon! Come follow me at {url}' }, + shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on #Mastodon! Come follow me at {url}' }, }); const mapStateToProps = state => ({ @@ -80,6 +81,60 @@ class CopyPasteText extends React.PureComponent { } +class TipCarousel extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + }; + + state = { + index: 0, + }; + + handleSwipe = index => { + this.setState({ index }); + }; + + handleChangeIndex = e => { + this.setState({ index: Number(e.currentTarget.getAttribute('data-index')) }); + }; + + handleKeyDown = e => { + switch(e.key) { + case 'ArrowLeft': + e.preventDefault(); + this.setState(({ index }, { children }) => ({ index: Math.abs(index - 1) % children.length })); + break; + case 'ArrowRight': + e.preventDefault(); + this.setState(({ index }, { children }) => ({ index: (index + 1) % children.length })); + break; + } + }; + + render () { + const { children } = this.props; + const { index } = this.state; + + return ( +
+ + {children} + + +
+ {children.map((_, i) => ( + + ))} +
+
+ ); + } + +} + class Share extends React.PureComponent { static propTypes = { @@ -105,6 +160,12 @@ class Share extends React.PureComponent { + +

+

+

+
+

diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 6d16ecd834..ca38088c64 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -3186,6 +3186,10 @@ }, { "descriptors": [ + { + "defaultMessage": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.", + "id": "onboarding.follows.empty" + }, { "defaultMessage": "Popular on Mastodon", "id": "onboarding.follows.title" @@ -3265,7 +3269,7 @@ { "descriptors": [ { - "defaultMessage": "I'm {username} on Mastodon! Come follow me at {url}", + "defaultMessage": "I'm {username} on #Mastodon! Come follow me at {url}", "id": "onboarding.share.message" }, { @@ -3284,6 +3288,18 @@ "defaultMessage": "Let people know how they can find you on Mastodon!", "id": "onboarding.share.lead" }, + { + "defaultMessage": "Did you know? You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!", + "id": "onboarding.tips.verification" + }, + { + "defaultMessage": "Did you know? If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!", + "id": "onboarding.tips.migration" + }, + { + "defaultMessage": "Did you know? You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!", + "id": "onboarding.tips.2fa" + }, { "defaultMessage": "Possible next steps:", "id": "onboarding.share.next_steps" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 55b34f3c87..177155497f 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -445,10 +445,11 @@ "onboarding.actions.close": "Don't show this screen again", "onboarding.actions.go_to_explore": "See what's trending", "onboarding.actions.go_to_home": "Go to your home feed", + "onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.title": "Popular on Mastodon", "onboarding.share.lead": "Let people know how they can find you on Mastodon!", - "onboarding.share.message": "I'm {username} on Mastodon! Come follow me at {url}", + "onboarding.share.message": "I'm {username} on #Mastodon! Come follow me at {url}", "onboarding.share.next_steps": "Possible next steps:", "onboarding.share.title": "Share your profile", "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", @@ -462,7 +463,10 @@ "onboarding.steps.setup_profile.title": "Customize your profile", "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!", "onboarding.steps.share_profile.title": "Share your profile", + "onboarding.tips.2fa": "Did you know? You can secure your account by setting up two-factor authentication in your account settings. It works with any TOTP app of your choice, no phone number necessary!", "onboarding.tips.accounts_from_other_servers": "Did you know? Since Mastodon is decentralized, some profiles you come across will be hosted on servers other than yours. And yet you can interact with them seamlessly! Their server is in the second half of their username!", + "onboarding.tips.migration": "Did you know? If you feel like {domain} is not a great server choice for you in the future, you can move to another Mastodon server without losing your followers. You can even host your own server!", + "onboarding.tips.verification": "Did you know? You can verify your account by putting a link to your Mastodon profile on your own website and adding the website to your profile. No fees or documents necessary!", "password_confirmation.exceeds_maxlength": "Password confirmation exceeds the maximum password length", "password_confirmation.mismatching": "Password confirmation does not match", "picture_in_picture.restore": "Put it back", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 248738f6b3..e985afea0b 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3026,6 +3026,29 @@ $ui-header-height: 55px; .account:last-child { border-bottom: 0; } + + &__empty { + text-align: center; + color: $darker-text-color; + font-weight: 500; + padding: 40px; + } +} + +.tip-carousel { + border: 1px solid transparent; + border-radius: 8px; + padding: 16px; + margin-bottom: 30px; + + &:focus { + outline: 0; + border-color: $highlight-text-color; + } + + .media-modal__pagination { + margin-bottom: 0; + } } .copy-paste-text { @@ -5539,6 +5562,11 @@ a.status-card.compact:hover { &.active { opacity: 1; } + + &:focus { + outline: 0; + background-color: $highlight-text-color; + } } .media-modal__close { From 9db152db38bc7c4b562f2ee96df6bfe18777eaee Mon Sep 17 00:00:00 2001 From: KMY Date: Wed, 26 Apr 2023 11:23:54 +0900 Subject: [PATCH 3/6] Fix queries count in fan_out antennas --- app/models/account_statuses_cleanup_policy.rb | 2 ++ app/models/antenna.rb | 34 +++++++++---------- app/services/fan_out_on_write_service.rb | 18 ++++++---- ...20230426013738_add_excludes_to_antennas.rb | 7 ++++ db/schema.rb | 7 +++- 5 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 db/migrate/20230426013738_add_excludes_to_antennas.rb diff --git a/app/models/account_statuses_cleanup_policy.rb b/app/models/account_statuses_cleanup_policy.rb index 14ce00abbc..8d507c48fd 100644 --- a/app/models/account_statuses_cleanup_policy.rb +++ b/app/models/account_statuses_cleanup_policy.rb @@ -18,6 +18,8 @@ # min_reblogs :integer # created_at :datetime not null # updated_at :datetime not null +# min_emojis :integer +# keep_self_emoji :boolean default(TRUE), not null # class AccountStatusesCleanupPolicy < ApplicationRecord include Redisable diff --git a/app/models/antenna.rb b/app/models/antenna.rb index 0b1d3c1d0e..0d7a218e02 100644 --- a/app/models/antenna.rb +++ b/app/models/antenna.rb @@ -17,6 +17,9 @@ # updated_at :datetime not null # expires_at :datetime # with_media_only :boolean default(FALSE), not null +# exclude_domains :jsonb +# exclude_accounts :jsonb +# exclude_tags :jsonb # class Antenna < ApplicationRecord include Expireable @@ -86,7 +89,7 @@ class Antenna < ApplicationRecord def keywords_raw=(raw) keywords = raw.split(/\R/).filter { |r| r.present? && r.length >= 2 }.uniq self[:keywords] = keywords - self[:any_keywords] = !keywords.any? && !exclude_keywords&.any? + self[:any_keywords] = !keywords.any? end def exclude_keywords_raw @@ -98,7 +101,6 @@ class Antenna < ApplicationRecord def exclude_keywords_raw=(raw) exclude_keywords = raw.split(/\R/).filter { |r| r.present? }.uniq self[:exclude_keywords] = exclude_keywords - self[:any_keywords] = !keywords&.any? && !exclude_keywords.any? end def tags_raw @@ -118,18 +120,19 @@ class Antenna < ApplicationRecord end def exclude_tags_raw - antenna_tags.where(exclude: true).map(&:tag).map(&:name).join("\n") + return '' if !exclude_tags.present? + Tag.where(id: exclude_tags).map(&:name).join("\n") end def exclude_tags_raw=(raw) return if exclude_tags_raw == raw + tags = [] tag_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('#') ? r[1..-1] : r }.uniq - - antenna_tags.where(exclude: true).destroy_all Tag.find_or_create_by_names(tag_names).each do |tag| - antenna_tags.create!(tag: tag, exclude: true) + tags << tag.id end + self[:exclude_tags] = tags end def domains_raw @@ -149,18 +152,15 @@ class Antenna < ApplicationRecord end def exclude_domains_raw - antenna_domains.where(exclude: true).map(&:name).join("\n") + return '' if !exclude_domains.present? + exclude_domains.join("\n") end def exclude_domains_raw=(raw) return if exclude_domains_raw == raw domain_names = raw.split(/\R/).filter { |r| r.present? }.uniq - - antenna_domains.where(exclude: true).destroy_all - domain_names.each do |domain| - antenna_domains.create!(name: domain, exclude: true) - end + self[:exclude_domains] = domain_names end def accounts_raw @@ -186,7 +186,8 @@ class Antenna < ApplicationRecord end def exclude_accounts_raw - antenna_accounts.where(exclude: true).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n") + return '' if !exclude_accounts.present? + Account.where(id: exclude_accounts).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n") end def exclude_accounts_raw=(raw) @@ -194,16 +195,15 @@ class Antenna < ApplicationRecord account_names = raw.split(/\R/).filter { |r| r.present? }.map { |r| r.start_with?('@') ? r[1..-1] : r }.uniq - hit = false - antenna_accounts.where(exclude: true).destroy_all + accounts = [] account_names.each do |name| username, domain = name.split('@') account = Account.find_by(username: username, domain: domain) if account.present? - antenna_accounts.create!(account: account, exclude: true) - hit = true + accounts << account.id end end + self[:exclude_accounts] = accounts end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index d29b051d0d..00ccf2dcc0 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -118,30 +118,34 @@ class FanOutOnWriteService < BaseService def deliver_to_antennas! lists = [] + tag_ids = @status.tags.pluck(:id) + domain = @account.domain || Rails.configuration.x.local_domain + antennas = Antenna.availables antennas = antennas.left_joins(:antenna_accounts).where(any_accounts: true).or(Antenna.availables.left_joins(:antenna_accounts) .where(antenna_accounts: { exclude: false, account: @status.account })) antennas = antennas.left_joins(:antenna_domains) .where(any_domains: true) .or(Antenna.availables.left_joins(:antenna_accounts).left_joins(:antenna_domains) .where(antenna_domains: { exclude: false, name: @status.account.domain })) antennas = antennas.left_joins(:antenna_tags) .where(any_tags: true) .or(Antenna.availables.left_joins(:antenna_accounts).left_joins(:antenna_domains).left_joins(:antenna_tags).where(antenna_tags: { exclude: false, tag: @status.tags })) antennas = antennas.where(account: @status.account.followers) if @status.visibility.to_sym == :unlisted antennas = antennas.where(with_media_only: false) if !@status.with_media? + antennas = antennas.where.not(account: @status.account.blocking) + antennas = antennas.includes(:antenna_accounts).includes(:antenna_domains).includes(:antenna_tags) antennas.in_batches do |ans| ans.each do |antenna| next if !antenna.enabled? - next if @status.account.blocking?(antenna.account) next if antenna.keywords.any? && !([nil, :public].include?(@status.searchability&.to_sym)) next if antenna.keywords.any? && !antenna.keywords.any? { |keyword| @status.text.include?(keyword) } - next if antenna.exclude_keywords.any? && antenna.exclude_keywords.any? { |keyword| @status.text.include?(keyword) } - next if antenna.antenna_accounts.where(exclude: true, account: @status.account).any? - next if antenna.antenna_domains.where(exclude: true, name: @status.account.domain).any? - next if antenna.antenna_tags.where(exclude: true, tag: @status.tags).any? - lists << antenna.list + next if antenna.exclude_keywords&.any? { |keyword| @status.text.include?(keyword) } + next if antenna.exclude_accounts&.include?(@status.account_id) + next if antenna.exclude_domains&.include?(domain) + next if antenna.exclude_tags&.any? { |tag_id| tag_ids.include?(tag_id) } + lists << antenna.list_id end end lists = lists.uniq if lists.any? FeedInsertWorker.push_bulk(lists) do |list| - [@status.id, list.id, 'list', { 'update' => update? }] + [@status.id, list, 'list', { 'update' => update? }] end end end diff --git a/db/migrate/20230426013738_add_excludes_to_antennas.rb b/db/migrate/20230426013738_add_excludes_to_antennas.rb new file mode 100644 index 0000000000..f38f53207b --- /dev/null +++ b/db/migrate/20230426013738_add_excludes_to_antennas.rb @@ -0,0 +1,7 @@ +class AddExcludesToAntennas < ActiveRecord::Migration[6.1] + def change + add_column :antennas, :exclude_domains, :jsonb + add_column :antennas, :exclude_accounts, :jsonb + add_column :antennas, :exclude_tags, :jsonb + end +end diff --git a/db/schema.rb b/db/schema.rb index e1f54b00c7..aef6f3736e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_04_23_233429) do +ActiveRecord::Schema.define(version: 2023_04_26_013738) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -117,6 +117,8 @@ ActiveRecord::Schema.define(version: 2023_04_23_233429) do t.integer "min_reblogs" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.integer "min_emojis" + t.boolean "keep_self_emoji", default: true, null: false t.index ["account_id"], name: "index_account_statuses_cleanup_policies_on_account_id" end @@ -300,6 +302,9 @@ ActiveRecord::Schema.define(version: 2023_04_23_233429) do t.datetime "updated_at", null: false t.datetime "expires_at" t.boolean "with_media_only", default: false, null: false + t.jsonb "exclude_domains" + t.jsonb "exclude_accounts" + t.jsonb "exclude_tags" t.index ["account_id"], name: "index_antennas_on_account_id" t.index ["any_accounts"], name: "index_antennas_on_any_accounts" t.index ["any_domains"], name: "index_antennas_on_any_domains" From ae2452abeaa94d5e06a5fd1ff2fec24253e3100e Mon Sep 17 00:00:00 2001 From: KMY Date: Wed, 26 Apr 2023 11:26:27 +0900 Subject: [PATCH 4/6] Remove translation text --- config/locales/ja.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 8c8e8ec21c..1457dedab2 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -955,7 +955,7 @@ ja: accounts_raw: 絞り込むアカウント available: 有効 description: アンテナは、サーバーが認識した全ての公開・ローカル公開投稿のうち、検索許可が「公開」または明示的に設定されていないもの(検索許可システムに対応していないサーバーからの投稿)、かつ購読を拒否していないすべてのアカウントからの投稿が対象です。検出された投稿は、指定したリストに追加されます。 - domains_hint: ドメインとは、アカウントIDやサイトのURLのうち「kmy.blue」「example.com」に該当する部分です。自身のサーバーを指定することはできません + domains_hint: ドメインとは、アカウントIDやサイトのURLのうち「kmy.blue」「example.com」に該当する部分です domains_raw: 絞り込むドメイン exclude_accounts_raw: 除外するアカウント exclude_domains_raw: 除外するドメイン From 8f8e0ec88c8c87746f12a157b18dc5983c834c5b Mon Sep 17 00:00:00 2001 From: KMY Date: Wed, 26 Apr 2023 11:30:36 +0900 Subject: [PATCH 5/6] Fix removing list with antenna --- app/controllers/api/v1/lists_controller.rb | 1 + config/locales/en.yml | 1 + config/locales/ja.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 843ca2ec2b..3c78862736 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -31,6 +31,7 @@ class Api::V1::ListsController < Api::BaseController end def destroy + raise Mastodon::ValidationError, I18n.t('antennas.errors.remove_list_with_antenna') if Antenna.where(list_id: @list.id).any? @list.destroy! render_empty end diff --git a/config/locales/en.yml b/config/locales/en.yml index efadfb8e92..adf772e152 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -994,6 +994,7 @@ en: empty_contexts: No contexts! You must set any context filters invalid_context: None or invalid context supplied invalid_list_owner: This list is not yours + remove_list_with_antenna: Cannot remove list because this list is related to antenna. index: contexts: Antennas in %{contexts} delete: Delete diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1457dedab2..1e97eabbf6 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -950,6 +950,7 @@ ja: errors: empty_contexts: 絞り込み条件が1つも指定されていないため無効です(除外条件はカウントされません) invalid_list_owner: これはあなたのリストではありません + remove_list_with_antenna: アンテナが関連付けられているリストは削除できません edit: accounts_hint: ローカルアカウントの場合は「@info」、リモートアカウントの場合は「@info@example.com」の形式で指定します。サーバーが認識していないアカウントは保存時に自動的に削除されます。 accounts_raw: 絞り込むアカウント From a18b0578bfd47917ed191891fad0c73e67139944 Mon Sep 17 00:00:00 2001 From: KMY Date: Wed, 26 Apr 2023 11:35:42 +0900 Subject: [PATCH 6/6] Remove translation phrases --- config/locales/ja.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1e97eabbf6..f5b4346f66 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -941,7 +941,7 @@ ja: hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は無害で、取り消すことができます。引っ越しは以前のアカウント側から開始する必要があります。 remove: エイリアスを削除 antennas: - beta: アンテナ機能はベータ版です。今後、予告なく全データリセット・機能削除を行う場合があります。この機能の存在は外部に積極的に宣伝しないよう、ご協力をお願いします。 + beta: アンテナ機能はベータ版です。今後、予告なく全データリセット・機能削除を行う場合があります。 contexts: account: アカウント domain: ドメイン