From 44073736debda0210b77a53d0f61a68783d28e7c Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 13 Mar 2025 19:44:23 +0900 Subject: [PATCH] enhance(frontend): improve preferences --- packages/frontend/src/boot/main-boot.ts | 1 + .../src/components/MkFollowButton.vue | 5 +- .../src/pages/settings/account-data.vue | 4 +- .../frontend/src/pages/settings/other.vue | 180 ++++++++---------- .../src/pages/settings/preferences.vue | 83 ++++---- packages/frontend/src/preferences/def.ts | 3 + packages/frontend/src/store.ts | 8 +- packages/frontend/src/style.scss | 2 +- .../utility/autogen/settings-search-index.ts | 43 +++-- 9 files changed, 164 insertions(+), 165 deletions(-) diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 0ad333b203..be72eeb9e1 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -191,6 +191,7 @@ export async function mainBoot() { prefer.commit('skipNoteRender', store.s.skipNoteRender); prefer.commit('showSoftWordMutedWord', store.s.showSoftWordMutedWord); prefer.commit('confirmOnReact', store.s.confirmOnReact); + prefer.commit('defaultFollowWithReplies', store.s.defaultWithReplies); prefer.commit('sound.masterVolume', store.s.sound_masterVolume); prefer.commit('sound.notUseSound', store.s.sound_notUseSound); prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive); diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 3d5d0ec5ab..a063854520 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -45,7 +45,6 @@ import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/utility/achievements.js'; import { pleaseLogin } from '@/utility/please-login.js'; import { $i } from '@/account.js'; -import { store } from '@/store.js'; import { prefer } from '@/preferences.js'; const props = withDefaults(defineProps<{ @@ -121,11 +120,11 @@ async function onClick() { } else { await misskeyApi('following/create', { userId: props.user.id, - withReplies: store.s.defaultWithReplies, + withReplies: prefer.s.defaultFollowWithReplies, }); emit('update:user', { ...props.user, - withReplies: store.s.defaultWithReplies, + withReplies: prefer.s.defaultFollowWithReplies, }); hasPendingFollowRequestFromYou.value = true; diff --git a/packages/frontend/src/pages/settings/account-data.vue b/packages/frontend/src/pages/settings/account-data.vue index 7e7036a8dc..ed5fe48821 100644 --- a/packages/frontend/src/pages/settings/account-data.vue +++ b/packages/frontend/src/pages/settings/account-data.vue @@ -168,12 +168,12 @@ import { selectFile } from '@/utility/select-file.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { $i } from '@/account.js'; -import { store } from '@/store.js'; import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import { prefer } from '@/preferences.js'; const excludeMutingUsers = ref(false); const excludeInactiveUsers = ref(false); -const withReplies = ref(store.s.defaultWithReplies); +const withReplies = ref(prefer.s.defaultFollowWithReplies); const onExportSuccess = () => { os.alert({ diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index b60db78071..62b0f5c941 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -16,112 +16,102 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch> --> - <FormSection first> - <div class="_gaps_s"> - <SearchMarker :keywords="['account', 'info']"> - <MkFolder> - <template #icon><i class="ti ti-info-circle"></i></template> - <template #label><SearchLabel>{{ i18n.ts.accountInfo }}</SearchLabel></template> + <div class="_gaps_s"> + <SearchMarker :keywords="['account', 'info']"> + <MkFolder> + <template #icon><i class="ti ti-info-circle"></i></template> + <template #label><SearchLabel>{{ i18n.ts.accountInfo }}</SearchLabel></template> - <div class="_gaps_m"> - <MkKeyValue> - <template #key>ID</template> - <template #value><span class="_monospace">{{ $i.id }}</span></template> - </MkKeyValue> + <div class="_gaps_m"> + <MkKeyValue> + <template #key>ID</template> + <template #value><span class="_monospace">{{ $i.id }}</span></template> + </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts.registeredDate }}</template> - <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> - </MkKeyValue> + <MkKeyValue> + <template #key>{{ i18n.ts.registeredDate }}</template> + <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> + </MkKeyValue> - <MkFolder> - <template #icon><i class="ti ti-badges"></i></template> - <template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template> + <MkFolder> + <template #icon><i class="ti ti-badges"></i></template> + <template #label><SearchLabel>{{ i18n.ts._role.policies }}</SearchLabel></template> - <div class="_gaps_s"> - <div v-for="policy in Object.keys($i.policies)" :key="policy"> - {{ policy }} ... {{ $i.policies[policy] }} - </div> + <div class="_gaps_s"> + <div v-for="policy in Object.keys($i.policies)" :key="policy"> + {{ policy }} ... {{ $i.policies[policy] }} </div> - </MkFolder> - </div> - </MkFolder> - </SearchMarker> + </div> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['roles']"> - <MkFolder> - <template #icon><i class="ti ti-badges"></i></template> - <template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template> + <SearchMarker :keywords="['roles']"> + <MkFolder> + <template #icon><i class="ti ti-badges"></i></template> + <template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template> - <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/> - </MkFolder> - </SearchMarker> + <MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['account', 'move', 'migration']"> - <MkFolder> - <template #icon><i class="ti ti-plane"></i></template> - <template #label><SearchLabel>{{ i18n.ts.accountMigration }}</SearchLabel></template> + <SearchMarker :keywords="['account', 'move', 'migration']"> + <MkFolder> + <template #icon><i class="ti ti-plane"></i></template> + <template #label><SearchLabel>{{ i18n.ts.accountMigration }}</SearchLabel></template> - <XMigration/> - </MkFolder> - </SearchMarker> + <XMigration/> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['account', 'close', 'delete']"> - <MkFolder> - <template #icon><i class="ti ti-alert-triangle"></i></template> - <template #label><SearchLabel>{{ i18n.ts.closeAccount }}</SearchLabel></template> + <SearchMarker :keywords="['account', 'close', 'delete']"> + <MkFolder> + <template #icon><i class="ti ti-alert-triangle"></i></template> + <template #label><SearchLabel>{{ i18n.ts.closeAccount }}</SearchLabel></template> - <div class="_gaps_m"> - <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> - <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton> - <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton> - </div> - </MkFolder> - </SearchMarker> + <div class="_gaps_m"> + <FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> + <MkButton v-if="!$i.isDeleted" danger @click="deleteAccount"><SearchKeyword>{{ i18n.ts._accountDelete.requestAccountDelete }}</SearchKeyword></MkButton> + <MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton> + </div> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['experimental', 'feature', 'flags']"> - <MkFolder> - <template #icon><i class="ti ti-flask"></i></template> - <template #label><SearchLabel>{{ i18n.ts.experimentalFeatures }}</SearchLabel></template> + <SearchMarker :keywords="['experimental', 'feature', 'flags']"> + <MkFolder> + <template #icon><i class="ti ti-flask"></i></template> + <template #label><SearchLabel>{{ i18n.ts.experimentalFeatures }}</SearchLabel></template> - <div class="_gaps_m"> - <MkSwitch v-model="enableCondensedLine"> - <template #label>Enable condensed line</template> - </MkSwitch> - <MkSwitch v-model="skipNoteRender"> - <template #label>Enable note render skipping</template> - </MkSwitch> - </div> - </MkFolder> - </SearchMarker> + <div class="_gaps_m"> + <MkSwitch v-model="enableCondensedLine"> + <template #label>Enable condensed line</template> + </MkSwitch> + <MkSwitch v-model="skipNoteRender"> + <template #label>Enable note render skipping</template> + </MkSwitch> + </div> + </MkFolder> + </SearchMarker> - <SearchMarker :keywords="['developer', 'mode', 'debug']"> - <MkFolder> - <template #icon><i class="ti ti-code"></i></template> - <template #label><SearchLabel>{{ i18n.ts.developer }}</SearchLabel></template> + <SearchMarker :keywords="['developer', 'mode', 'debug']"> + <MkFolder> + <template #icon><i class="ti ti-code"></i></template> + <template #label><SearchLabel>{{ i18n.ts.developer }}</SearchLabel></template> - <div class="_gaps_m"> - <MkSwitch v-model="devMode"> - <template #label>{{ i18n.ts.devMode }}</template> - </MkSwitch> - </div> - </MkFolder> - </SearchMarker> - </div> - </FormSection> + <div class="_gaps_m"> + <MkSwitch v-model="devMode"> + <template #label>{{ i18n.ts.devMode }}</template> + </MkSwitch> + </div> + </MkFolder> + </SearchMarker> + </div> - <FormSection> - <FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink> - </FormSection> + <hr> - <FormSection> - <div class="_gaps_s"> - <MkSwitch v-model="defaultWithReplies">{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</MkSwitch> - <MkButton danger @click="updateRepliesAll(true)"><i class="ti ti-messages"></i> {{ i18n.ts.showRepliesToOthersInTimelineAll }}</MkButton> - <MkButton danger @click="updateRepliesAll(false)"><i class="ti ti-messages-off"></i> {{ i18n.ts.hideRepliesToOthersInTimelineAll }}</MkButton> - </div> - </FormSection> + <FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink> </div> </SearchMarker> </template> @@ -137,7 +127,6 @@ import MkKeyValue from '@/components/MkKeyValue.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { store } from '@/store.js'; import { signout, signinRequired } from '@/account.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; @@ -152,7 +141,6 @@ const reportError = prefer.model('reportError'); const enableCondensedLine = prefer.model('enableCondensedLine'); const skipNoteRender = prefer.model('skipNoteRender'); const devMode = prefer.model('devMode'); -const defaultWithReplies = computed(store.makeGetterSetter('defaultWithReplies')); watch(skipNoteRender, async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); @@ -182,16 +170,6 @@ async function deleteAccount() { await signout(); } -async function updateRepliesAll(withReplies: boolean) { - const { canceled } = await os.confirm({ - type: 'warning', - text: withReplies ? i18n.ts.confirmShowRepliesAll : i18n.ts.confirmHideRepliesAll, - }); - if (canceled) return; - - misskeyApi('following/update-all', { withReplies }); -} - const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 94d154e9c7..87d80602ad 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -393,6 +393,39 @@ SPDX-License-Identifier: AGPL-3.0-only </MkFolder> </SearchMarker> + <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> + <SearchMarker :keywords="['other']"> <MkFolder> <template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template> @@ -422,6 +455,14 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </MkPreferenceContainer> </SearchMarker> + + <SearchMarker :keywords="['follow', 'replies']"> + <MkPreferenceContainer k="defaultFollowWithReplies"> + <MkSwitch v-model="defaultFollowWithReplies"> + <template #label><SearchLabel>{{ i18n.ts.withRepliesByDefaultForNewlyFollowed }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> </div> <SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']"> @@ -477,43 +518,14 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkFolder> </SearchMarker> + </div> - <SearchMarker :keywords="['datasaver']"> - <MkFolder> - <template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template> + <hr> - <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> + <div class="_gaps_s"> + <FormLink to="/settings/navbar"><template #icon><i class="ti ti-list"></i></template>{{ i18n.ts.navbar }}</FormLink> + <FormLink to="/settings/statusbar"><template #icon><i class="ti ti-list"></i></template>{{ i18n.ts.statusbar }}</FormLink> + <FormLink to="/settings/deck"><template #icon><i class="ti ti-columns"></i></template>{{ i18n.ts.deck }}</FormLink> <FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink> </div> </div> @@ -592,6 +604,7 @@ const nsfw = prefer.model('nsfw'); const emojiStyle = prefer.model('emojiStyle'); const useBlurEffectForModal = prefer.model('useBlurEffectForModal'); const useBlurEffect = prefer.model('useBlurEffect'); +const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies'); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 6a926c4b26..eb3d6eeac4 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -306,6 +306,9 @@ export const PREF_DEF = { confirmOnReact: { default: false, }, + defaultFollowWithReplies: { + default: false, + }, plugins: { default: [] as Plugin[], }, diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 6eebcd1ead..9a61e63d0e 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -100,10 +100,6 @@ export const store = markRaw(new Storage('base', { where: 'device', default: {} as Record<string, Record<string, string[]>>, }, - defaultWithReplies: { - where: 'account', - default: false, - }, pluginTokens: { where: 'deviceAccount', default: {} as Record<string, string>, // plugin id, token @@ -119,6 +115,10 @@ export const store = markRaw(new Storage('base', { }, //#region TODO: そのうち消す (preferに移行済み) + defaultWithReplies: { + where: 'account', + default: false, + }, reactions: { where: 'account', default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index 48aacf10bc..fb2c805b1b 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -128,7 +128,7 @@ optgroup, option { } hr { - margin: var(--MI-margin) 0 var(--MI-margin) 0; + margin: 0; border: none; height: 1px; background: var(--MI_THEME-divider); diff --git a/packages/frontend/src/utility/autogen/settings-search-index.ts b/packages/frontend/src/utility/autogen/settings-search-index.ts index e44910e850..ebc67eb58d 100644 --- a/packages/frontend/src/utility/autogen/settings-search-index.ts +++ b/packages/frontend/src/utility/autogen/settings-search-index.ts @@ -482,44 +482,54 @@ export const searchIndexes: SearchIndexItem[] = [ }, { id: '2E7vdIUQd', + label: i18n.ts.dataSaver, + keywords: ['datasaver'], + }, + { + id: '6ZbRRIhA6', children: [ { - id: 'C2iXtZKb3', + id: 'soNZaKfiW', label: i18n.ts.squareAvatars, keywords: ['avatar', 'icon', 'square'], }, { - id: 'DCfJg0bva', + id: 'nhwHJJ2tl', label: i18n.ts.seasonalScreenEffect, keywords: ['effect', 'show'], }, { - id: 'AV0iGW0vg', + id: 'oMAVUuxTm', label: i18n.ts.openImageInNewTab, keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'], }, { - id: '5h8vhCX1S', + id: 'hSqX5JKM7', + label: i18n.ts.withRepliesByDefaultForNewlyFollowed, + keywords: ['follow', 'replies'], + }, + { + id: 'fm98eqzke', label: i18n.ts.whenServerDisconnected, keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'], }, { - id: 'zZxyXHk3A', + id: '1rWDVig8Y', label: i18n.ts.numberOfPageCache, keywords: ['cache', 'page'], }, { - id: '7ix3kvMyU', + id: 'vXLtihtCp', label: i18n.ts.forceShowAds, keywords: ['ad', 'show'], }, { - id: '6RxgjmMLN', + id: '77YljFpiH', label: i18n.ts.hemisphere, keywords: [], }, { - id: '5iMpm5rES', + id: 'CZgDNPP1h', label: i18n.ts.additionalEmojiDictionary, keywords: ['emoji', 'dictionary', 'additional', 'extra'], }, @@ -527,11 +537,6 @@ export const searchIndexes: SearchIndexItem[] = [ label: i18n.ts.other, keywords: ['other'], }, - { - id: 'fnR7PRww5', - label: i18n.ts.dataSaver, - keywords: ['datasaver'], - }, ], label: i18n.ts.preferences, keywords: ['general', 'preferences', i18n.ts._settings.preferencesBanner], @@ -549,32 +554,32 @@ export const searchIndexes: SearchIndexItem[] = [ id: 'F1uK9ssiY', children: [ { - id: 'msAcN6u3S', + id: 'E0ndmaP6Q', label: i18n.ts._role.policies, keywords: ['account', 'info'], }, { - id: 'pbTLsgRO7', + id: 'r5SjfwZJc', label: i18n.ts.rolesAssignedToMe, keywords: ['roles'], }, { - id: 'fQpvZyfLK', + id: 'cm7LrjgaW', label: i18n.ts.accountMigration, keywords: ['account', 'move', 'migration'], }, { - id: 'xhfur5m2z', + id: 'ozfqNviP3', label: i18n.ts.closeAccount, keywords: ['account', 'close', 'delete', i18n.ts._accountDelete.requestAccountDelete], }, { - id: 'oAXB8zm2U', + id: 'tpywgkpxy', label: i18n.ts.experimentalFeatures, keywords: ['experimental', 'feature', 'flags'], }, { - id: '95OjjGSo7', + id: '54wETGawJ', label: i18n.ts.developer, keywords: ['developer', 'mode', 'debug'], },