From 5d228fb0f32aca9337c8b8a9ea9544f28d981f34 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 13 Mar 2025 17:39:53 +0900 Subject: [PATCH] enhance(frontend): re-organize settings page --- locales/index.d.ts | 4 + locales/ja-JP.yml | 1 + .../src/pages/settings/accessibility.vue | 51 ++ .../src/pages/settings/appearance.vue | 325 --------- .../frontend/src/pages/settings/index.vue | 5 - .../src/pages/settings/preferences.vue | 668 ++++++++++++------ packages/frontend/src/router/definition.ts | 4 - .../utility/autogen/settings-search-index.ts | 423 ++++++----- 8 files changed, 702 insertions(+), 779 deletions(-) delete mode 100644 packages/frontend/src/pages/settings/appearance.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index b814bb70e1..f579aadb5d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5342,6 +5342,10 @@ export interface Locale extends ILocale { * 絵文字パレット */ "emojiPalette": string; + /** + * 投稿フォーム + */ + "postForm": string; "_emojiPalette": { /** * パレット diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b51a839715..2151a06611 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1331,6 +1331,7 @@ preferenceSyncConflictChoiceDevice: "デバイスの設定値" preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル" paste: "ペースト" emojiPalette: "絵文字パレット" +postForm: "投稿フォーム" _emojiPalette: palettes: "パレット" diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue index 3dbb039a17..f7b1e7d2a0 100644 --- a/packages/frontend/src/pages/settings/accessibility.vue +++ b/packages/frontend/src/pages/settings/accessibility.vue @@ -60,6 +60,17 @@ SPDX-License-Identifier: AGPL-3.0-only </SearchMarker> </div> + <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> + <MkPreferenceContainer k="menuStyle"> + <MkSelect v-model="menuStyle"> + <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + <SearchMarker :keywords="['contextmenu', 'system', 'native']"> <MkPreferenceContainer k="contextMenu"> <MkSelect v-model="contextMenu"> @@ -70,6 +81,22 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSelect> </MkPreferenceContainer> </SearchMarker> + + <SearchMarker :keywords="['font', 'size']"> + <MkRadios v-model="fontSize"> + <template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template> + <option :value="null"><span style="font-size: 14px;">Aa</span></option> + <option value="1"><span style="font-size: 15px;">Aa</span></option> + <option value="2"><span style="font-size: 16px;">Aa</span></option> + <option value="3"><span style="font-size: 17px;">Aa</span></option> + </MkRadios> + </SearchMarker> + + <SearchMarker :keywords="['font', 'system', 'native']"> + <MkSwitch v-model="useSystemFont"> + <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template> + </MkSwitch> + </SearchMarker> </div> </SearchMarker> </template> @@ -84,6 +111,8 @@ import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import { miLocalStorage } from '@/local-storage.js'; +import MkRadios from '@/components/MkRadios.vue'; const reduceAnimation = prefer.model('animation', v => !v, v => !v); const animatedMfm = prefer.model('animatedMfm'); @@ -92,10 +121,32 @@ const keepScreenOn = prefer.model('keepScreenOn'); const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe'); const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); const contextMenu = prefer.model('contextMenu'); +const menuStyle = prefer.model('menuStyle'); + +const fontSize = ref(miLocalStorage.getItem('fontSize')); +const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); + +watch(fontSize, () => { + if (fontSize.value == null) { + miLocalStorage.removeItem('fontSize'); + } else { + miLocalStorage.setItem('fontSize', fontSize.value); + } +}); + +watch(useSystemFont, () => { + if (useSystemFont.value) { + miLocalStorage.setItem('useSystemFont', 't'); + } else { + miLocalStorage.removeItem('useSystemFont'); + } +}); watch([ keepScreenOn, contextMenu, + fontSize, + useSystemFont, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); diff --git a/packages/frontend/src/pages/settings/appearance.vue b/packages/frontend/src/pages/settings/appearance.vue deleted file mode 100644 index 3fda5bc4c8..0000000000 --- a/packages/frontend/src/pages/settings/appearance.vue +++ /dev/null @@ -1,325 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<SearchMarker path="/settings/appearance" :label="i18n.ts.appearance" :keywords="['appearance']" icon="ti ti-device-desktop"> - <div class="_gaps_m"> - <MkFeatureBanner icon="/client-assets/desktop_computer_3d.png" color="#eaff00"> - <SearchKeyword>{{ i18n.ts._settings.appearanceBanner }}</SearchKeyword> - </MkFeatureBanner> - - <FormSection first> - <div class="_gaps_m"> - <div class="_gaps_s"> - <SearchMarker :keywords="['blur']"> - <MkPreferenceContainer k="useBlurEffect"> - <MkSwitch v-model="useBlurEffect"> - <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['blur', 'modal']"> - <MkPreferenceContainer k="useBlurEffectForModal"> - <MkSwitch v-model="useBlurEffectForModal"> - <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']"> - <MkPreferenceContainer k="highlightSensitiveMedia"> - <MkSwitch v-model="highlightSensitiveMedia"> - <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['avatar', 'icon', 'square']"> - <MkPreferenceContainer k="squareAvatars"> - <MkSwitch v-model="squareAvatars"> - <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']"> - <MkPreferenceContainer k="showAvatarDecorations"> - <MkSwitch v-model="showAvatarDecorations"> - <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['note', 'timeline', 'gap']"> - <MkPreferenceContainer k="showGapBetweenNotesInTimeline"> - <MkSwitch v-model="showGapBetweenNotesInTimeline"> - <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['effect', 'show']"> - <MkPreferenceContainer k="enableSeasonalScreenEffect"> - <MkSwitch v-model="enableSeasonalScreenEffect"> - <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - - <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> - <MkPreferenceContainer k="menuStyle"> - <MkSelect v-model="menuStyle"> - <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template> - <option value="auto">{{ i18n.ts.auto }}</option> - <option value="popup">{{ i18n.ts.popup }}</option> - <option value="drawer">{{ i18n.ts.drawer }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']"> - <MkPreferenceContainer k="emojiStyle"> - <div> - <MkRadios v-model="emojiStyle"> - <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template> - <option value="native">{{ i18n.ts.native }}</option> - <option value="fluentEmoji">Fluent Emoji</option> - <option value="twemoji">Twemoji</option> - </MkRadios> - <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> - </div> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['font', 'size']"> - <MkRadios v-model="fontSize"> - <template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template> - <option :value="null"><span style="font-size: 14px;">Aa</span></option> - <option value="1"><span style="font-size: 15px;">Aa</span></option> - <option value="2"><span style="font-size: 16px;">Aa</span></option> - <option value="3"><span style="font-size: 17px;">Aa</span></option> - </MkRadios> - </SearchMarker> - - <SearchMarker :keywords="['font', 'system', 'native']"> - <MkSwitch v-model="useSystemFont"> - <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template> - </MkSwitch> - </SearchMarker> - </div> - </FormSection> - - <SearchMarker :keywords="['note', 'display']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.displayOfNote }}</SearchLabel></template> - - <div class="_gaps_m"> - <SearchMarker :keywords="['reaction', 'size', 'scale', 'display']"> - <MkPreferenceContainer k="reactionsDisplaySize"> - <MkRadios v-model="reactionsDisplaySize"> - <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template> - <option value="small">{{ i18n.ts.small }}</option> - <option value="medium">{{ i18n.ts.medium }}</option> - <option value="large">{{ i18n.ts.large }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']"> - <MkPreferenceContainer k="limitWidthOfReaction"> - <MkSwitch v-model="limitWidthOfReaction"> - <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']"> - <MkPreferenceContainer k="mediaListWithOneImageAppearance"> - <MkRadios v-model="mediaListWithOneImageAppearance"> - <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template> - <option value="expand">{{ i18n.ts.default }}</option> - <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option> - <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option> - <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']"> - <MkPreferenceContainer k="instanceTicker"> - <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker"> - <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template> - <option value="none">{{ i18n.ts._instanceTicker.none }}</option> - <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> - <option value="always">{{ i18n.ts._instanceTicker.always }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']"> - <MkPreferenceContainer k="nsfw"> - <MkSelect v-model="nsfw"> - <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template> - <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option> - <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option> - <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option> - </MkSelect> - </MkPreferenceContainer> - </SearchMarker> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['notification', 'display']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.notificationDisplay }}</SearchLabel></template> - - <div class="_gaps_m"> - <SearchMarker :keywords="['position']"> - <MkPreferenceContainer k="notificationPosition"> - <MkRadios v-model="notificationPosition"> - <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template> - <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> - <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> - <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> - <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['stack', 'axis', 'direction']"> - <MkPreferenceContainer k="notificationStackAxis"> - <MkRadios v-model="notificationStackAxis"> - <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template> - <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> - <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> - </MkRadios> - </MkPreferenceContainer> - </SearchMarker> - - <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton> - </div> - </FormSection> - </SearchMarker> - - <FormSection> - <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> - </FormSection> - </div> -</SearchMarker> -</template> - -<script lang="ts" setup> -import { computed, ref, watch } from 'vue'; -import * as Misskey from 'misskey-js'; -import MkSwitch from '@/components/MkSwitch.vue'; -import MkSelect from '@/components/MkSelect.vue'; -import MkRadios from '@/components/MkRadios.vue'; -import { prefer } from '@/preferences.js'; -import { reloadAsk } from '@/utility/reload-ask.js'; -import { i18n } from '@/i18n.js'; -import { definePage } from '@/page.js'; -import { miLocalStorage } from '@/local-storage.js'; -import FormLink from '@/components/form/link.vue'; -import { globalEvents } from '@/events.js'; -import { claimAchievement } from '@/utility/achievements.js'; -import MkButton from '@/components/MkButton.vue'; -import FormSection from '@/components/form/section.vue'; -import { instance } from '@/instance.js'; -import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; -import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; - -const fontSize = ref(miLocalStorage.getItem('fontSize')); -const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); - -const showAvatarDecorations = prefer.model('showAvatarDecorations'); -const emojiStyle = prefer.model('emojiStyle'); -const menuStyle = prefer.model('menuStyle'); -const useBlurEffectForModal = prefer.model('useBlurEffectForModal'); -const useBlurEffect = prefer.model('useBlurEffect'); -const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia'); -const squareAvatars = prefer.model('squareAvatars'); -const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect'); -const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline'); -const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance'); -const reactionsDisplaySize = prefer.model('reactionsDisplaySize'); -const limitWidthOfReaction = prefer.model('limitWidthOfReaction'); -const notificationPosition = prefer.model('notificationPosition'); -const notificationStackAxis = prefer.model('notificationStackAxis'); -const nsfw = prefer.model('nsfw'); -const instanceTicker = prefer.model('instanceTicker'); - -watch(fontSize, () => { - if (fontSize.value == null) { - miLocalStorage.removeItem('fontSize'); - } else { - miLocalStorage.setItem('fontSize', fontSize.value); - } -}); - -watch(useSystemFont, () => { - if (useSystemFont.value) { - miLocalStorage.setItem('useSystemFont', 't'); - } else { - miLocalStorage.removeItem('useSystemFont'); - } -}); - -watch([ - fontSize, - useSystemFont, - squareAvatars, - highlightSensitiveMedia, - enableSeasonalScreenEffect, - showGapBetweenNotesInTimeline, - mediaListWithOneImageAppearance, - reactionsDisplaySize, - limitWidthOfReaction, - mediaListWithOneImageAppearance, - reactionsDisplaySize, - limitWidthOfReaction, - instanceTicker, -], async () => { - await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); -}); - -let smashCount = 0; -let smashTimer: number | null = null; - -function testNotification(): void { - const notification: Misskey.entities.Notification = { - id: Math.random().toString(), - createdAt: new Date().toUTCString(), - isRead: false, - type: 'test', - }; - - globalEvents.emit('clientNotification', notification); - - // セルフ通知破壊 実績関連 - smashCount++; - if (smashCount >= 10) { - claimAchievement('smashTestNotificationButton'); - smashCount = 0; - } - if (smashTimer) { - clearTimeout(smashTimer); - } - smashTimer = window.setTimeout(() => { - smashCount = 0; - }, 300); -} - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePage(() => ({ - title: i18n.ts.appearance, - icon: 'ti ti-device-desktop', -})); -</script> diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index debcd4bd3e..3b7c44fbfe 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -118,11 +118,6 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ text: i18n.ts.emojiPalette, to: '/settings/emoji-palette', active: currentPage.value?.route.name === 'emoji-palette', - }, { - icon: 'ti ti-device-desktop', - text: i18n.ts.appearance, - to: '/settings/appearance', - active: currentPage.value?.route.name === 'appearance', }, { icon: 'ti ti-music', text: i18n.ts.sounds, diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 374477c510..b9a596067c 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -10,121 +10,174 @@ SPDX-License-Identifier: AGPL-3.0-only <SearchKeyword>{{ i18n.ts._settings.preferencesBanner }}</SearchKeyword> </MkFeatureBanner> - <SearchMarker :keywords="['language']"> - <MkSelect v-model="lang"> - <template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template> - <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> - <template #caption> - <I18n :src="i18n.ts.i18nInfo" tag="span"> - <template #link> - <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> - </template> - </I18n> - </template> - </MkSelect> - </SearchMarker> - - <SearchMarker :keywords="['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop']"> - <MkRadios v-model="overridedDeviceKind"> - <template #label><SearchLabel>{{ i18n.ts.overridedDeviceKind }}</SearchLabel></template> - <option :value="null">{{ i18n.ts.auto }}</option> - <option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option> - <option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option> - <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option> - </MkRadios> - </SearchMarker> - - <FormSection> - <div class="_gaps_s"> - <SearchMarker :keywords="['post', 'form', 'timeline']"> - <MkPreferenceContainer k="showFixedPostForm"> - <MkSwitch v-model="showFixedPostForm"> - <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['post', 'form', 'timeline', 'channel']"> - <MkPreferenceContainer k="showFixedPostFormInChannel"> - <MkSwitch v-model="showFixedPostFormInChannel"> - <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['pinned', 'list']"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template> - <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ --> - <MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> - <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> - </MkFolder> - </SearchMarker> - - <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']"> - <MkPreferenceContainer k="enableQuickAddMfmFunction"> - <MkSwitch v-model="enableQuickAddMfmFunction"> - <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - </FormSection> - - <FormSection> - <div class="_gaps_m"> - <SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']"> - <MkPreferenceContainer k="rememberNoteVisibility"> - <MkSwitch v-model="rememberNoteVisibility"> - <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - - <SearchMarker :keywords="['default', 'note', 'visibility']"> - <MkDisableSection :disabled="rememberNoteVisibility"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template> - <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> - <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template> - <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template> - <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template> - - <div class="_gaps_m"> - <MkPreferenceContainer k="defaultNoteVisibility"> - <MkSelect v-model="defaultNoteVisibility"> - <option value="public">{{ i18n.ts._visibility.public }}</option> - <option value="home">{{ i18n.ts._visibility.home }}</option> - <option value="followers">{{ i18n.ts._visibility.followers }}</option> - <option value="specified">{{ i18n.ts._visibility.specified }}</option> - </MkSelect> - </MkPreferenceContainer> - - <MkPreferenceContainer k="defaultNoteLocalOnly"> - <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> - </MkPreferenceContainer> - </div> - </MkFolder> - </MkDisableSection> - </SearchMarker> - </div> - </FormSection> - - <SearchMarker :keywords="['note']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.note }}</SearchLabel></template> + <SearchMarker :keywords="['general']"> + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.general }}</SearchLabel></template> <div class="_gaps_m"> + <SearchMarker :keywords="['language']"> + <MkSelect v-model="lang"> + <template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template> + <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> + <template #caption> + <I18n :src="i18n.ts.i18nInfo" tag="span"> + <template #link> + <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> + </template> + </I18n> + </template> + </MkSelect> + </SearchMarker> + + <SearchMarker :keywords="['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop']"> + <MkRadios v-model="overridedDeviceKind"> + <template #label><SearchLabel>{{ i18n.ts.overridedDeviceKind }}</SearchLabel></template> + <option :value="null">{{ i18n.ts.auto }}</option> + <option value="smartphone"><i class="ti ti-device-mobile"/> {{ i18n.ts.smartphone }}</option> + <option value="tablet"><i class="ti ti-device-tablet"/> {{ i18n.ts.tablet }}</option> + <option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option> + </MkRadios> + </SearchMarker> + <div class="_gaps_s"> - <SearchMarker :keywords="['renote']"> - <MkPreferenceContainer k="collapseRenotes"> - <MkSwitch v-model="collapseRenotes"> - <template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template> - <template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template> + <SearchMarker :keywords="['blur']"> + <MkPreferenceContainer k="useBlurEffect"> + <MkSwitch v-model="useBlurEffect"> + <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> </MkSwitch> </MkPreferenceContainer> </SearchMarker> + <SearchMarker :keywords="['blur', 'modal']"> + <MkPreferenceContainer k="useBlurEffectForModal"> + <MkSwitch v-model="useBlurEffectForModal"> + <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']"> + <MkPreferenceContainer k="showAvatarDecorations"> + <MkSwitch v-model="showAvatarDecorations"> + <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['follow', 'confirm', 'always']"> + <MkPreferenceContainer k="alwaysConfirmFollow"> + <MkSwitch v-model="alwaysConfirmFollow"> + <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']"> + <MkPreferenceContainer k="highlightSensitiveMedia"> + <MkSwitch v-model="highlightSensitiveMedia"> + <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']"> + <MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia"> + <MkSwitch v-model="confirmWhenRevealingSensitiveMedia"> + <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + + <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']"> + <MkPreferenceContainer k="emojiStyle"> + <div> + <MkRadios v-model="emojiStyle"> + <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template> + <option value="native">{{ i18n.ts.native }}</option> + <option value="fluentEmoji">Fluent Emoji</option> + <option value="twemoji">Twemoji</option> + </MkRadios> + <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> + </div> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['pinned', 'list']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template> + <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ --> + <MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> + <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> + </MkFolder> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['timeline']"> + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.timeline }}</SearchLabel></template> + + <div class="_gaps_s"> + <SearchMarker :keywords="['post', 'form', 'timeline']"> + <MkPreferenceContainer k="showFixedPostForm"> + <MkSwitch v-model="showFixedPostForm"> + <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['post', 'form', 'timeline', 'channel']"> + <MkPreferenceContainer k="showFixedPostFormInChannel"> + <MkSwitch v-model="showFixedPostFormInChannel"> + <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['renote']"> + <MkPreferenceContainer k="collapseRenotes"> + <MkSwitch v-model="collapseRenotes"> + <template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template> + <template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['note', 'timeline', 'gap']"> + <MkPreferenceContainer k="showGapBetweenNotesInTimeline"> + <MkSwitch v-model="showGapBetweenNotesInTimeline"> + <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['load', 'auto', 'more']"> + <MkPreferenceContainer k="enableInfiniteScroll"> + <MkSwitch v-model="enableInfiniteScroll"> + <template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['disable', 'streaming', 'timeline']"> + <MkPreferenceContainer k="disableStreamingTimeline"> + <MkSwitch v-model="disableStreamingTimeline"> + <template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['note']"> + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.note }}</SearchLabel></template> + + <div class="_gaps_m"> + <div class="_gaps_s"> <SearchMarker :keywords="['hover', 'show', 'footer', 'action']"> <MkPreferenceContainer k="showNoteActionsOnlyHover"> <MkSwitch v-model="showNoteActionsOnlyHover"> @@ -157,6 +210,14 @@ SPDX-License-Identifier: AGPL-3.0-only </MkPreferenceContainer> </SearchMarker> + <SearchMarker :keywords="['reaction', 'confirm']"> + <MkPreferenceContainer k="confirmOnReact"> + <MkSwitch v-model="confirmOnReact"> + <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']"> <MkPreferenceContainer k="loadRawImages"> <MkSwitch v-model="loadRawImages"> @@ -164,40 +225,6 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkPreferenceContainer> </SearchMarker> - </div> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['notification']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template> - - <div class="_gaps_m"> - <SearchMarker :keywords="['group']"> - <MkPreferenceContainer k="useGroupedNotifications"> - <MkSwitch v-model="useGroupedNotifications"> - <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['behavior']"> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.behavior }}</SearchLabel></template> - - <div class="_gaps_m"> - <div class="_gaps_s"> - <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']"> - <MkPreferenceContainer k="imageNewTab"> - <MkSwitch v-model="imageNewTab"> - <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> <SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']"> <MkPreferenceContainer k="useReactionPickerForContextMenu"> @@ -206,47 +233,70 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkPreferenceContainer> </SearchMarker> + </div> - <SearchMarker :keywords="['load', 'auto', 'more']"> - <MkPreferenceContainer k="enableInfiniteScroll"> - <MkSwitch v-model="enableInfiniteScroll"> - <template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['reaction', 'size', 'scale', 'display']"> + <MkPreferenceContainer k="reactionsDisplaySize"> + <MkRadios v-model="reactionsDisplaySize"> + <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template> + <option value="small">{{ i18n.ts.small }}</option> + <option value="medium">{{ i18n.ts.medium }}</option> + <option value="large">{{ i18n.ts.large }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> - <SearchMarker :keywords="['disable', 'streaming', 'timeline']"> - <MkPreferenceContainer k="disableStreamingTimeline"> - <MkSwitch v-model="disableStreamingTimeline"> - <template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']"> + <MkPreferenceContainer k="limitWidthOfReaction"> + <MkSwitch v-model="limitWidthOfReaction"> + <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> - <SearchMarker :keywords="['follow', 'confirm', 'always']"> - <MkPreferenceContainer k="alwaysConfirmFollow"> - <MkSwitch v-model="alwaysConfirmFollow"> - <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']"> + <MkPreferenceContainer k="mediaListWithOneImageAppearance"> + <MkRadios v-model="mediaListWithOneImageAppearance"> + <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template> + <option value="expand">{{ i18n.ts.default }}</option> + <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option> + <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option> + <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> - <SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']"> - <MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia"> - <MkSwitch v-model="confirmWhenRevealingSensitiveMedia"> - <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']"> + <MkPreferenceContainer k="instanceTicker"> + <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker"> + <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> - <SearchMarker :keywords="['reaction', 'confirm']"> - <MkPreferenceContainer k="confirmOnReact"> - <MkSwitch v-model="confirmOnReact"> - <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template> - </MkSwitch> - </MkPreferenceContainer> - </SearchMarker> + <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']"> + <MkPreferenceContainer k="nsfw"> + <MkSelect v-model="nsfw"> + <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template> + <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option> + <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option> + <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option> + </MkSelect> + </MkPreferenceContainer> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + <SearchMarker :keywords="['post', 'form']"> + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.postForm }}</SearchLabel></template> + + <div class="_gaps_m"> + <div class="_gaps_s"> <SearchMarker :keywords="['remember', 'keep', 'note', 'cw']"> <MkPreferenceContainer k="keepCw"> <MkSwitch v-model="keepCw"> @@ -254,6 +304,123 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkPreferenceContainer> </SearchMarker> + + <SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']"> + <MkPreferenceContainer k="rememberNoteVisibility"> + <MkSwitch v-model="rememberNoteVisibility"> + <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']"> + <MkPreferenceContainer k="enableQuickAddMfmFunction"> + <MkSwitch v-model="enableQuickAddMfmFunction"> + <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + </div> + + <SearchMarker :keywords="['default', 'note', 'visibility']"> + <MkDisableSection :disabled="rememberNoteVisibility"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template> + <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> + <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template> + <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template> + <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template> + + <div class="_gaps_m"> + <MkPreferenceContainer k="defaultNoteVisibility"> + <MkSelect v-model="defaultNoteVisibility"> + <option value="public">{{ i18n.ts._visibility.public }}</option> + <option value="home">{{ i18n.ts._visibility.home }}</option> + <option value="followers">{{ i18n.ts._visibility.followers }}</option> + <option value="specified">{{ i18n.ts._visibility.specified }}</option> + </MkSelect> + </MkPreferenceContainer> + + <MkPreferenceContainer k="defaultNoteLocalOnly"> + <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> + </MkPreferenceContainer> + </div> + </MkFolder> + </MkDisableSection> + </SearchMarker> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['notification']"> + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.notifications }}</SearchLabel></template> + + <div class="_gaps_m"> + <SearchMarker :keywords="['group']"> + <MkPreferenceContainer k="useGroupedNotifications"> + <MkSwitch v-model="useGroupedNotifications"> + <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['position']"> + <MkPreferenceContainer k="notificationPosition"> + <MkRadios v-model="notificationPosition"> + <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template> + <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> + <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> + <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> + <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['stack', 'axis', 'direction']"> + <MkPreferenceContainer k="notificationStackAxis"> + <MkRadios v-model="notificationStackAxis"> + <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template> + <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> + <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> + </MkRadios> + </MkPreferenceContainer> + </SearchMarker> + + <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['other']"> + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template> + + <div class="_gaps_m"> + <div class="_gaps_s"> + <SearchMarker :keywords="['avatar', 'icon', 'square']"> + <MkPreferenceContainer k="squareAvatars"> + <MkSwitch v-model="squareAvatars"> + <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['effect', 'show']"> + <MkPreferenceContainer k="enableSeasonalScreenEffect"> + <MkSwitch v-model="enableSeasonalScreenEffect"> + <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']"> + <MkPreferenceContainer k="imageNewTab"> + <MkSwitch v-model="imageNewTab"> + <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> </div> <SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']"> @@ -276,47 +443,6 @@ SPDX-License-Identifier: AGPL-3.0-only </MkPreferenceContainer> </SearchMarker> - <SearchMarker :label="i18n.ts.dataSaver" :keywords="['datasaver']"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template> - - <div class="_gaps_m"> - <MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo> - - <div class="_buttons"> - <MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton> - <MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton> - </div> - <div class="_gaps_m"> - <MkSwitch v-model="dataSaver.media"> - {{ i18n.ts._dataSaver._media.title }} - <template #caption>{{ i18n.ts._dataSaver._media.description }}</template> - </MkSwitch> - <MkSwitch v-model="dataSaver.avatar"> - {{ i18n.ts._dataSaver._avatar.title }} - <template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template> - </MkSwitch> - <MkSwitch v-model="dataSaver.urlPreview"> - {{ i18n.ts._dataSaver._urlPreview.title }} - <template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template> - </MkSwitch> - <MkSwitch v-model="dataSaver.code"> - {{ i18n.ts._dataSaver._code.title }} - <template #caption>{{ i18n.ts._dataSaver._code.description }}</template> - </MkSwitch> - </div> - </div> - </MkFolder> - </SearchMarker> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker> - <FormSection> - <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template> - - <div class="_gaps"> <SearchMarker :keywords="['ad', 'show']"> <MkPreferenceContainer k="forceShowAds"> <MkSwitch v-model="forceShowAds"> @@ -347,18 +473,47 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkFolder> </SearchMarker> - - <FormLink to="/settings/navbar">{{ i18n.ts.navbar }}</FormLink> - <FormLink to="/settings/statusbar">{{ i18n.ts.statusbar }}</FormLink> </div> - </FormSection> + </MkFolder> </SearchMarker> - <FormSection> - <div class="_gaps"> - <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink> - </div> - </FormSection> + <SearchMarker :keywords="['datasaver']"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template> + + <div class="_gaps_m"> + <MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo> + + <div class="_buttons"> + <MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton> + <MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton> + </div> + <div class="_gaps_m"> + <MkSwitch v-model="dataSaver.media"> + {{ i18n.ts._dataSaver._media.title }} + <template #caption>{{ i18n.ts._dataSaver._media.description }}</template> + </MkSwitch> + <MkSwitch v-model="dataSaver.avatar"> + {{ i18n.ts._dataSaver._avatar.title }} + <template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template> + </MkSwitch> + <MkSwitch v-model="dataSaver.urlPreview"> + {{ i18n.ts._dataSaver._urlPreview.title }} + <template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template> + </MkSwitch> + <MkSwitch v-model="dataSaver.code"> + {{ i18n.ts._dataSaver._code.title }} + <template #caption>{{ i18n.ts._dataSaver._code.description }}</template> + </MkSwitch> + </div> + </div> + </MkFolder> + </SearchMarker> + + <FormLink to="/settings/navbar">{{ i18n.ts.navbar }}</FormLink> + <FormLink to="/settings/statusbar">{{ i18n.ts.statusbar }}</FormLink> + <FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink> + <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> </div> </SearchMarker> </template> @@ -366,6 +521,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref, watch } from 'vue'; import { langs } from '@@/js/config.js'; +import * as Misskey from 'misskey-js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkRadios from '@/components/MkRadios.vue'; @@ -386,6 +542,9 @@ import { miLocalStorage } from '@/local-storage.js'; import { prefer } from '@/preferences.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import { globalEvents } from '@/events.js'; +import { claimAchievement } from '@/utility/achievements.js'; +import { instance } from '@/instance.js'; const lang = ref(miLocalStorage.getItem('lang')); const dataSaver = ref(prefer.s.dataSaver); @@ -413,10 +572,24 @@ const useGroupedNotifications = prefer.model('useGroupedNotifications'); const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow'); const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia'); const confirmOnReact = prefer.model('confirmOnReact'); -const contextMenu = prefer.model('contextMenu'); const defaultNoteVisibility = prefer.model('defaultNoteVisibility'); const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly'); const rememberNoteVisibility = prefer.model('rememberNoteVisibility'); +const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline'); +const notificationPosition = prefer.model('notificationPosition'); +const notificationStackAxis = prefer.model('notificationStackAxis'); +const instanceTicker = prefer.model('instanceTicker'); +const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia'); +const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance'); +const reactionsDisplaySize = prefer.model('reactionsDisplaySize'); +const limitWidthOfReaction = prefer.model('limitWidthOfReaction'); +const squareAvatars = prefer.model('squareAvatars'); +const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect'); +const showAvatarDecorations = prefer.model('showAvatarDecorations'); +const nsfw = prefer.model('nsfw'); +const emojiStyle = prefer.model('emojiStyle'); +const useBlurEffectForModal = prefer.model('useBlurEffectForModal'); +const useBlurEffect = prefer.model('useBlurEffect'); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -433,7 +606,17 @@ watch([ disableStreamingTimeline, alwaysConfirmFollow, confirmWhenRevealingSensitiveMedia, - contextMenu, + showGapBetweenNotesInTimeline, + mediaListWithOneImageAppearance, + reactionsDisplaySize, + limitWidthOfReaction, + mediaListWithOneImageAppearance, + reactionsDisplaySize, + limitWidthOfReaction, + instanceTicker, + squareAvatars, + highlightSensitiveMedia, + enableSeasonalScreenEffect, ], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); @@ -522,6 +705,33 @@ watch(dataSaver, (to) => { deep: true, }); +let smashCount = 0; +let smashTimer: number | null = null; + +function testNotification(): void { + const notification: Misskey.entities.Notification = { + id: Math.random().toString(), + createdAt: new Date().toUTCString(), + isRead: false, + type: 'test', + }; + + globalEvents.emit('clientNotification', notification); + + // セルフ通知破壊 実績関連 + smashCount++; + if (smashCount >= 10) { + claimAchievement('smashTestNotificationButton'); + smashCount = 0; + } + if (smashTimer) { + clearTimeout(smashTimer); + } + smashTimer = window.setTimeout(() => { + smashCount = 0; + }, 300); +} + const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index 752356497e..62b13d22be 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -105,10 +105,6 @@ const routes: RouteDef[] = [{ path: '/theme', name: 'theme', component: page(() => import('@/pages/settings/theme.vue')), - }, { - path: '/appearance', - name: 'appearance', - component: page(() => import('@/pages/settings/appearance.vue')), }, { path: '/navbar', name: 'navbar', diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index 4f1a94f266..734dc0c99c 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -272,176 +272,265 @@ export const searchIndexes: SearchIndexItem[] = [ children: [ { id: 'kMJ5laK3n', - label: i18n.ts.uiLanguage, - keywords: ['language'], - }, - { - id: 'dlKebHH6k', - label: i18n.ts.overridedDeviceKind, - keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'], - }, - { - id: 'nxvMUir3T', - label: i18n.ts.showFixedPostForm, - keywords: ['post', 'form', 'timeline'], - }, - { - id: '84MdeDWL1', - label: i18n.ts.showFixedPostFormInChannel, - keywords: ['post', 'form', 'timeline', 'channel'], - }, - { - id: 'dOig3ye4Z', - label: i18n.ts.pinnedList, - keywords: ['pinned', 'list'], - }, - { - id: '4huRldNp5', - label: i18n.ts.enableQuickAddMfmFunction, - keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'], - }, - { - id: '1x3JNXj8N', - label: i18n.ts.rememberNoteVisibility, - keywords: ['remember', 'keep', 'note', 'visibility'], - }, - { - id: 'CfAg0Qekq', - label: i18n.ts.defaultNoteVisibility, - keywords: ['default', 'note', 'visibility'], - }, - { - id: 'tMm9kH9gy', children: [ { - id: 'hDdVkBFJP', + id: 'EC8J177N8', + label: i18n.ts.uiLanguage, + keywords: ['language'], + }, + { + id: 'CHKy9gnrh', + label: i18n.ts.overridedDeviceKind, + keywords: ['device', 'type', 'kind', 'smartphone', 'tablet', 'desktop'], + }, + { + id: 'snyCQ5oKE', + label: i18n.ts.useBlurEffect, + keywords: ['blur'], + }, + { + id: '8j36S4Ev6', + label: i18n.ts.useBlurEffectForModal, + keywords: ['blur', 'modal'], + }, + { + id: 'cytWLyF1V', + label: i18n.ts.showAvatarDecorations, + keywords: ['avatar', 'icon', 'decoration', 'show'], + }, + { + id: 'odi1d2SWy', + label: i18n.ts.alwaysConfirmFollow, + keywords: ['follow', 'confirm', 'always'], + }, + { + id: 'm43Eu3Ypg', + label: i18n.ts.highlightSensitiveMedia, + keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'], + }, + { + id: 'cjfAtxMzP', + label: i18n.ts.confirmWhenRevealingSensitiveMedia, + keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'], + }, + { + id: 'aefexW9fD', + label: i18n.ts.emojiStyle, + keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], + }, + { + id: 'p7aiLj6A0', + label: i18n.ts.pinnedList, + keywords: ['pinned', 'list'], + }, + ], + label: i18n.ts.general, + keywords: ['general'], + }, + { + id: 'khT3n6byY', + children: [ + { + id: 'DftdlLbNu', + label: i18n.ts.showFixedPostForm, + keywords: ['post', 'form', 'timeline'], + }, + { + id: 'FbhoeuRAD', + label: i18n.ts.showFixedPostFormInChannel, + keywords: ['post', 'form', 'timeline', 'channel'], + }, + { + id: 'rq69GTeB4', label: i18n.ts.collapseRenotes, keywords: ['renote', i18n.ts.collapseRenotesDescription], }, { - id: 'uJJyDABGu', + id: 'omxZk3eET', + label: i18n.ts.showGapBetweenNotesInTimeline, + keywords: ['note', 'timeline', 'gap'], + }, + { + id: 'epvi2Nv2G', + label: i18n.ts.enableInfiniteScroll, + keywords: ['load', 'auto', 'more'], + }, + { + id: 'v26JSj9mH', + label: i18n.ts.disableStreamingTimeline, + keywords: ['disable', 'streaming', 'timeline'], + }, + ], + label: i18n.ts.timeline, + keywords: ['timeline'], + }, + { + id: '7Uf8ksn3q', + children: [ + { + id: 'tLGyaQagB', label: i18n.ts.showNoteActionsOnlyHover, keywords: ['hover', 'show', 'footer', 'action'], }, { - id: 'ufc2X9voy', + id: '7W6g8Dcqz', label: i18n.ts.showClipButtonInNoteFooter, keywords: ['footer', 'action', 'clip', 'show'], }, { - id: '7Jwvu8bK6', + id: 'uAOoH3LFF', label: i18n.ts.enableAdvancedMfm, keywords: ['mfm', 'enable', 'show', 'advanced'], }, { - id: 'yb11lSY1G', + id: 'eCiyZLC8n', label: i18n.ts.showReactionsCount, keywords: ['reaction', 'count', 'show'], }, { - id: 'fL49Zxe9i', + id: '68u9uRmFP', + label: i18n.ts.confirmOnReact, + keywords: ['reaction', 'confirm'], + }, + { + id: 'rHWm4sXIe', label: i18n.ts.loadRawImages, keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'], }, + { + id: '9L2XGJw7e', + label: i18n.ts.useReactionPickerForContextMenu, + keywords: ['reaction', 'picker', 'contextmenu', 'open'], + }, + { + id: 'uIMCIK7kG', + label: i18n.ts.reactionsDisplaySize, + keywords: ['reaction', 'size', 'scale', 'display'], + }, + { + id: 'uMckjO9bz', + label: i18n.ts.limitWidthOfReaction, + keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'], + }, + { + id: 'yeghU4qiH', + label: i18n.ts.mediaListWithOneImageAppearance, + keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'], + }, + { + id: 'yYSOPoAKE', + label: i18n.ts.instanceTicker, + keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'], + }, + { + id: 'iOHiIu32L', + label: i18n.ts.displayOfSensitiveMedia, + keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'], + }, ], label: i18n.ts.note, keywords: ['note'], }, { - id: 'bUOs2UKY4', + id: 'zrJicawH9', children: [ { - id: 'c8gA9Xj2a', + id: 'iuEuPe6pa', + label: i18n.ts.keepCw, + keywords: ['remember', 'keep', 'note', 'cw'], + }, + { + id: '9WrGgANqN', + label: i18n.ts.rememberNoteVisibility, + keywords: ['remember', 'keep', 'note', 'visibility'], + }, + { + id: 'Cu7ErCM7C', + label: i18n.ts.enableQuickAddMfmFunction, + keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'], + }, + { + id: 'oQl8xwiyI', + label: i18n.ts.defaultNoteVisibility, + keywords: ['default', 'note', 'visibility'], + }, + ], + label: i18n.ts.postForm, + keywords: ['post', 'form'], + }, + { + id: 'xFmAg2tDe', + children: [ + { + id: 'mepqKL5Ow', label: i18n.ts.useGroupedNotifications, keywords: ['group'], }, + { + id: 'wUuUOEO1g', + label: i18n.ts.position, + keywords: ['position'], + }, + { + id: '27em8eC8R', + label: i18n.ts.stackAxis, + keywords: ['stack', 'axis', 'direction'], + }, ], label: i18n.ts.notifications, keywords: ['notification'], }, { - id: 'tjGzqy3qa', + id: 'AzymHsnrp', children: [ { - id: '3OeHscv45', + id: 'DFUrEO2DI', + label: i18n.ts.squareAvatars, + keywords: ['avatar', 'icon', 'square'], + }, + { + id: 'r9DX60AxL', + label: i18n.ts.seasonalScreenEffect, + keywords: ['effect', 'show'], + }, + { + id: 'sJ3fqncSD', label: i18n.ts.openImageInNewTab, keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'], }, { - id: 'bFsNusspF', - label: i18n.ts.useReactionPickerForContextMenu, - keywords: ['reaction', 'picker', 'contextmenu', 'open'], - }, - { - id: '2h3rY1izt', - label: i18n.ts.enableInfiniteScroll, - keywords: ['load', 'auto', 'more'], - }, - { - id: 'pkK3eeFKm', - label: i18n.ts.disableStreamingTimeline, - keywords: ['disable', 'streaming', 'timeline'], - }, - { - id: 'y2v7CV9zs', - label: i18n.ts.alwaysConfirmFollow, - keywords: ['follow', 'confirm', 'always'], - }, - { - id: 'A8a5hcLce', - label: i18n.ts.confirmWhenRevealingSensitiveMedia, - keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'], - }, - { - id: 'utFrfuW7X', - label: i18n.ts.confirmOnReact, - keywords: ['reaction', 'confirm'], - }, - { - id: 'kmdsnVIQX', - label: i18n.ts.keepCw, - keywords: ['remember', 'keep', 'note', 'cw'], - }, - { - id: 'mNRK0pt8L', + id: 'p7s0hwZ8A', label: i18n.ts.whenServerDisconnected, keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'], }, { - id: 'vE7KeV4U4', + id: 'yCleENWNf', label: i18n.ts.numberOfPageCache, keywords: ['cache', 'page'], }, { - id: 'eJ2jme16W', - label: i18n.ts.dataSaver, - keywords: ['datasaver'], - }, - ], - label: i18n.ts.behavior, - keywords: ['behavior'], - }, - { - id: 'F3kpUNvSQ', - children: [ - { - id: '4bfFRM0UD', + id: 'omEy5Q3Ev', label: i18n.ts.forceShowAds, keywords: ['ad', 'show'], }, { - id: '2pB0jWBHo', + id: 'aWitQSBtD', label: i18n.ts.hemisphere, keywords: [], }, { - id: 'eIvnR6Xxo', + id: 'hUQAXl1H4', label: i18n.ts.additionalEmojiDictionary, keywords: ['emoji', 'dictionary', 'additional', 'extra'], }, ], label: i18n.ts.other, - keywords: [], + keywords: ['other'], + }, + { + id: 'aSbKFHbOy', + label: i18n.ts.dataSaver, + keywords: ['datasaver'], }, ], label: i18n.ts.preferences, @@ -715,119 +804,6 @@ export const searchIndexes: SearchIndexItem[] = [ path: '/settings/avatar-decoration', icon: 'ti ti-sparkles', }, - { - id: 'AqPvMgn3A', - children: [ - { - id: '1wtOIwAdm', - label: i18n.ts.useBlurEffect, - keywords: ['blur'], - }, - { - id: '6fLNMTwNt', - label: i18n.ts.useBlurEffectForModal, - keywords: ['blur', 'modal'], - }, - { - id: 'E0WXhhRB1', - label: i18n.ts.highlightSensitiveMedia, - keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'], - }, - { - id: '7iZsGkplG', - label: i18n.ts.squareAvatars, - keywords: ['avatar', 'icon', 'square'], - }, - { - id: 'AfRMcC6IM', - label: i18n.ts.showAvatarDecorations, - keywords: ['avatar', 'icon', 'decoration', 'show'], - }, - { - id: 'i7aSaEWaT', - label: i18n.ts.showGapBetweenNotesInTimeline, - keywords: ['note', 'timeline', 'gap'], - }, - { - id: 'knj98Mx84', - label: i18n.ts.seasonalScreenEffect, - keywords: ['effect', 'show'], - }, - { - id: 'Bzg77rYNd', - label: i18n.ts.menuStyle, - keywords: ['menu', 'style', 'popup', 'drawer'], - }, - { - id: '7AOZ1ZgDv', - label: i18n.ts.emojiStyle, - keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'], - }, - { - id: 'fDelHUrBi', - label: i18n.ts.fontSize, - keywords: ['font', 'size'], - }, - { - id: 'siOW5aSwp', - label: i18n.ts.useSystemFont, - keywords: ['font', 'system', 'native'], - }, - { - id: 's05dHQ1dW', - children: [ - { - id: 'zoMbYCvP0', - label: i18n.ts.reactionsDisplaySize, - keywords: ['reaction', 'size', 'scale', 'display'], - }, - { - id: 'lGFzLnWfB', - label: i18n.ts.limitWidthOfReaction, - keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'], - }, - { - id: '9E0v8VKIY', - label: i18n.ts.mediaListWithOneImageAppearance, - keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'], - }, - { - id: 'xB7MPEF4Q', - label: i18n.ts.instanceTicker, - keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'], - }, - { - id: '7siYCSodm', - label: i18n.ts.displayOfSensitiveMedia, - keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'], - }, - ], - label: i18n.ts.displayOfNote, - keywords: ['note', 'display'], - }, - { - id: 'uQfyiHMSs', - children: [ - { - id: 'y3uTXsSQ6', - label: i18n.ts.position, - keywords: ['position'], - }, - { - id: 'PILAdkVM', - label: i18n.ts.stackAxis, - keywords: ['stack', 'axis', 'direction'], - }, - ], - label: i18n.ts.notificationDisplay, - keywords: ['notification', 'display'], - }, - ], - label: i18n.ts.appearance, - keywords: ['appearance', i18n.ts._settings.appearanceBanner], - path: '/settings/appearance', - icon: 'ti ti-device-desktop', - }, { id: '330Q4mf8E', children: [ @@ -912,9 +888,24 @@ export const searchIndexes: SearchIndexItem[] = [ }, { id: '1fV9WINCQ', + label: i18n.ts.menuStyle, + keywords: ['menu', 'style', 'popup', 'drawer'], + }, + { + id: 'mLQzlKUNu', label: i18n.ts._contextMenu.title, keywords: ['contextmenu', 'system', 'native'], }, + { + id: 'yP96aA3j9', + label: i18n.ts.fontSize, + keywords: ['font', 'size'], + }, + { + id: 'jQeiMopFE', + label: i18n.ts.useSystemFont, + keywords: ['font', 'system', 'native'], + }, ], label: i18n.ts.accessibility, keywords: ['accessibility', i18n.ts._settings.accessibilityBanner],