diff options
Diffstat (limited to 'packages/frontend/src/pages/settings')
17 files changed, 91 insertions, 31 deletions
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index b7d648c1a4..6a9a1e16e2 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -108,9 +108,11 @@ async function registerTOTP(): Promise<void> { token: auth.result.token, }); - os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), { + const { dispose } = os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), { twoFactorData, - }, {}, 'closed'); + }, { + closed: () => dispose(), + }); } async function unregisterTOTP(): Promise<void> { diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index 1182346de9..08c9261dcf 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -74,22 +74,24 @@ async function removeAccount(account) { } function addExistingAccount() { - os.popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { done: async res => { await addAccounts(res.id, res.i); os.success(); init(); }, - }, 'closed'); + closed: () => dispose(), + }); } function createAccount() { - os.popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { done: async res => { await addAccounts(res.id, res.i); switchAccountWithToken(res.i); }, - }, 'closed'); + closed: () => dispose(), + }); } async function switchAccount(account: any) { diff --git a/packages/frontend/src/pages/settings/api.vue b/packages/frontend/src/pages/settings/api.vue index d9596b4e45..b35d406a98 100644 --- a/packages/frontend/src/pages/settings/api.vue +++ b/packages/frontend/src/pages/settings/api.vue @@ -23,7 +23,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; const isDesktop = ref(window.innerWidth >= 1100); function generateToken() { - os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, { done: async result => { const { name, permissions } = result; const { token } = await misskeyApi('miauth/gen-token', { @@ -38,7 +38,8 @@ function generateToken() { text: token, }); }, - }, 'closed'); + closed: () => dispose(), + }); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index 3cc911c014..77229d3349 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -67,7 +67,7 @@ misskeyApi('get-avatar-decorations').then(_avatarDecorations => { }); function openDecoration(avatarDecoration, index?: number) { - os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), { + const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), { decoration: avatarDecoration, usingIndex: index, }, { @@ -108,7 +108,8 @@ function openDecoration(avatarDecoration, index?: number) { }); $i.avatarDecorations = update; }, - }, 'closed'); + closed: () => dispose(), + }); } function detachAllDecorations() { diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue index 0b0b932f46..3f7db1b779 100644 --- a/packages/frontend/src/pages/settings/drive-cleaner.vue +++ b/packages/frontend/src/pages/settings/drive-cleaner.vue @@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script setup lang="ts"> -import { computed, ref, watch } from 'vue'; +import { computed, ref, watch, type StyleValue } from 'vue'; import tinycolor from 'tinycolor2'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; @@ -102,10 +102,10 @@ function fetchDriveInfo(): void { }); } -function genUsageBar(fsize: number): object { +function genUsageBar(fsize: number): StyleValue { return { width: `${fsize / usage.value * 100}%`, - background: tinycolor({ h: 180 - (fsize / usage.value * 180), s: 0.7, l: 0.5 }), + background: tinycolor({ h: 180 - (fsize / usage.value * 180), s: 0.7, l: 0.5 }).toHslString(), }; } diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index bcaa048de2..fa09637844 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -90,7 +90,7 @@ const meterStyle = computed(() => { h: 180 - (usage.value / capacity.value * 180), s: 0.7, l: 0.5, - }), + }).toHslString(), }; }); diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index b48308b96f..47681e6cde 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -234,6 +234,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch> <MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch> <MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch> + <MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch> </div> <MkSelect v-model="serverDisconnectedBehavior"> <template #label>{{ i18n.ts.whenServerDisconnected }}</template> @@ -241,6 +242,12 @@ SPDX-License-Identifier: AGPL-3.0-only <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> <option value="disabled">{{ i18n.ts._serverDisconnectedBehavior.disabled }}</option> </MkSelect> + <MkSelect v-model="contextMenu"> + <template #label>{{ i18n.ts._contextMenu.title }}</template> + <option value="app">{{ i18n.ts._contextMenu.app }}</option> + <option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option> + <option value="native">{{ i18n.ts._contextMenu.native }}</option> + </MkSelect> <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> <template #label>{{ i18n.ts.numberOfPageCache }}</template> <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> @@ -426,6 +433,8 @@ const visibilityOnBoost = computed(defaultStore.makeGetterSetter('visibilityOnBo const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe')); const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer')); const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow')); +const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia')); +const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu')); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -485,6 +494,8 @@ watch([ showVisibilitySelectorOnBoost, visibilityOnBoost, alwaysConfirmFollow, + confirmWhenRevealingSensitiveMedia, + contextMenu, ], async () => { await reloadAsk(); }); diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 9804454e66..3c3dcfe41e 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -82,7 +82,7 @@ import MkCode from '@/components/MkCode.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import * as os from '@/os.js'; -import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { ColdDeviceStorage } from '@/store.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 00cd64dd9f..f9fd494ce9 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -118,8 +118,6 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'sound_note', 'sound_noteMy', 'sound_notification', - 'sound_antenna', - 'sound_channel', ]; const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [ 'lightTheme', diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 4ffa367365..6cc19db127 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -481,6 +481,7 @@ definePageMetadata(() => ({ &:hover, &:focus { opacity: .7; } + &:active { cursor: pointer; } diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index 113abd708b..81478fede5 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -9,7 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.sound }}</template> <option v-for="x in soundsTypes" :key="x ?? 'null'" :value="x">{{ getSoundTypeName(x) }}</option> </MkSelect> - <div v-if="type === '_driveFile_'" :class="$style.fileSelectorRoot"> + <div v-if="type === '_driveFile_' && driveFileError === true" :class="$style.fileSelectorRoot"> + <MkButton :class="$style.fileSelectorButton" inline rounded primary @click="selectSound">{{ i18n.ts.selectFile }}</MkButton> + <div :class="$style.fileErrorRoot"> + <MkCondensedLine>{{ i18n.ts._soundSettings.driveFileError }}</MkCondensedLine> + </div> + </div> + <div v-else-if="type === '_driveFile_'" :class="$style.fileSelectorRoot"> <MkButton :class="$style.fileSelectorButton" inline rounded primary @click="selectSound">{{ i18n.ts.selectFile }}</MkButton> <div :class="['_nowrap', !fileUrl && $style.fileNotSelected]">{{ friendlyFileName }}</div> </div> @@ -19,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_buttons"> <MkButton inline @click="listen"><i class="ti ti-player-play"></i> {{ i18n.ts.listen }}</MkButton> - <MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <MkButton inline primary :disabled="!hasChanged || driveFileError" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </div> </div> </template> <script lang="ts" setup> -import { ref, computed } from 'vue'; +import { ref, computed, watch } from 'vue'; import type { SoundType } from '@/scripts/sound.js'; import MkSelect from '@/components/MkSelect.vue'; import MkButton from '@/components/MkButton.vue'; @@ -51,13 +57,18 @@ const type = ref<SoundType>(props.type); const fileId = ref(props.fileId); const fileUrl = ref(props.fileUrl); const fileName = ref<string>(''); +const driveFileError = ref(false); +const hasChanged = ref(false); const volume = ref(props.volume); if (type.value === '_driveFile_' && fileId.value) { - const apiRes = await misskeyApi('drive/files/show', { + await misskeyApi('drive/files/show', { fileId: fileId.value, + }).then((res) => { + fileName.value = res.name; + }).catch((res) => { + driveFileError.value = true; }); - fileName.value = apiRes.name; } function getSoundTypeName(f: SoundType): string { @@ -107,9 +118,21 @@ function selectSound(ev) { fileUrl.value = file.url; fileName.value = file.name; fileId.value = file.id; + driveFileError.value = false; + hasChanged.value = true; }); } +watch([type, volume], ([typeTo, volumeTo], [typeFrom, volumeFrom]) => { + if (typeFrom !== typeTo && typeTo !== '_driveFile_') { + fileUrl.value = undefined; + fileName.value = ''; + fileId.value = undefined; + driveFileError.value = false; + } + hasChanged.value = true; +}); + function listen() { if (type.value === '_driveFile_' && (!fileUrl.value || !fileId.value)) { os.alert({ @@ -131,6 +154,10 @@ function listen() { } function save() { + if (hasChanged.value === false || driveFileError.value === true) { + return; + } + if (type.value === '_driveFile_' && !fileUrl.value) { os.alert({ type: 'warning', @@ -163,6 +190,13 @@ function save() { gap: 8px; } +.fileErrorRoot { + flex-grow: 1; + min-width: 0; + font-weight: 700; + color: var(--error); +} + .fileSelectorButton { flex-shrink: 0; } diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index 090f0cf14c..9fcf564e55 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -21,8 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only <MkFolder v-for="type in operationTypes" :key="type"> <template #label>{{ i18n.ts._sfx[type] }}</template> <template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template> - - <XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/> + <Suspense> + <template #default> + <XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/> + </template> + <template #fallback> + <MkLoading/> + </template> + </Suspense> </MkFolder> </div> </FormSection> @@ -54,8 +60,6 @@ const sounds = ref<Record<OperationType, Ref<SoundStore>>>({ note: defaultStore.reactiveState.sound_note, noteMy: defaultStore.reactiveState.sound_noteMy, notification: defaultStore.reactiveState.sound_notification, - antenna: defaultStore.reactiveState.sound_antenna, - channel: defaultStore.reactiveState.sound_channel, reaction: defaultStore.reactiveState.sound_reaction, }); diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue index 92e389a288..67943524ef 100644 --- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="statusbar.props.shuffle"> <template #label>{{ i18n.ts.shuffle }}</template> </MkSwitch> - <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number"> + <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1"> <template #label>{{ i18n.ts.refreshInterval }}</template> </MkInput> <MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1"> @@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </template> <template v-else-if="statusbar.type === 'federation'"> - <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number"> + <MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1"> <template #label>{{ i18n.ts.refreshInterval }}</template> </MkInput> <MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1"> diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue index 8a94d7388b..579ca6b20b 100644 --- a/packages/frontend/src/pages/settings/theme.manage.vue +++ b/packages/frontend/src/pages/settings/theme.manage.vue @@ -38,7 +38,7 @@ import MkSelect from '@/components/MkSelect.vue'; import MkInput from '@/components/MkInput.vue'; import MkButton from '@/components/MkButton.vue'; import { Theme, getBuiltinThemesRef } from '@/scripts/theme.js'; -import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import * as os from '@/os.js'; import { getThemes, removeTheme } from '@/theme-store.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index eaa9b5b97e..ad07a6b539 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -213,12 +213,18 @@ definePageMetadata(() => ({ } } + .dn:focus-visible ~ .toggle { + outline: 2px solid var(--focus); + outline-offset: 2px; + } + .toggle { cursor: pointer; display: inline-block; position: relative; width: 90px; height: 50px; + margin: 4px; // focus用のアウトライン background-color: #83D8FF; border-radius: 90px - 6; transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue index e9fb1e471e..058ef69c35 100644 --- a/packages/frontend/src/pages/settings/webhook.edit.vue +++ b/packages/frontend/src/pages/settings/webhook.edit.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> <FormSection> - <template #label>{{ i18n.ts._webhookSettings.events }}</template> + <template #label>{{ i18n.ts._webhookSettings.trigger }}</template> <div class="_gaps_s"> <MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch> diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue index 5bf85e48f4..d62357caaf 100644 --- a/packages/frontend/src/pages/settings/webhook.new.vue +++ b/packages/frontend/src/pages/settings/webhook.new.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> <FormSection> - <template #label>{{ i18n.ts._webhookSettings.events }}</template> + <template #label>{{ i18n.ts._webhookSettings.trigger }}</template> <div class="_gaps_s"> <MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch> |