diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-11 14:52:04 +0900 |
|---|---|---|
| committer | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-11 14:52:04 +0900 |
| commit | d185785f20a2c3e58054fd61afd94dd05ba0b207 (patch) | |
| tree | 328551ec9cfa4864ce36d5b914d8b440c582ef1f /packages/frontend/src/pages | |
| parent | 🎨 (diff) | |
| download | misskey-d185785f20a2c3e58054fd61afd94dd05ba0b207.tar.gz misskey-d185785f20a2c3e58054fd61afd94dd05ba0b207.tar.bz2 misskey-d185785f20a2c3e58054fd61afd94dd05ba0b207.zip | |
enhance(frontend): improve settings page
Diffstat (limited to 'packages/frontend/src/pages')
| -rw-r--r-- | packages/frontend/src/pages/settings/accessibility.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/account-data.vue | 277 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/api.vue | 53 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/appearance.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/connect.vue | 112 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/drive.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/import-export.vue | 263 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/index.vue | 19 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/mute-block.vue | 285 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/notifications.vue | 7 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/plugin.vue | 7 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/preferences.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/privacy.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/security.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/sounds.vue | 5 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/webhook.vue | 57 |
16 files changed, 589 insertions, 526 deletions
diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue index f22e45ce1f..3dbb039a17 100644 --- a/packages/frontend/src/pages/settings/accessibility.vue +++ b/packages/frontend/src/pages/settings/accessibility.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/accessibility" :label="i18n.ts.accessibility" :keywords="['accessibility']" icon="ti ti-accessible"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/mens_room_3d.png" color="#0011ff"> + <SearchKeyword>{{ i18n.ts._settings.accessibilityBanner }}</SearchKeyword> + </MkFeatureBanner> + <div class="_gaps_s"> <SearchMarker :keywords="['animation', 'motion', 'reduce']"> <MkPreferenceContainer k="animation"> @@ -79,6 +83,7 @@ import { reloadAsk } from '@/utility/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const reduceAnimation = prefer.model('animation', v => !v, v => !v); const animatedMfm = prefer.model('animatedMfm'); diff --git a/packages/frontend/src/pages/settings/account-data.vue b/packages/frontend/src/pages/settings/account-data.vue new file mode 100644 index 0000000000..8df545a2a3 --- /dev/null +++ b/packages/frontend/src/pages/settings/account-data.vue @@ -0,0 +1,277 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<SearchMarker path="/settings/account-data" :label="i18n.ts._settings.accountData" :keywords="['import', 'export', 'data']" icon="ti ti-package"> + <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/package_3d.png" color="#ff9100"> + <SearchKeyword>{{ i18n.ts._settings.accountDataBanner }}</SearchKeyword> + </MkFeatureBanner> + + <div class="_gaps_s"> + <SearchMarker :keywords="['notes']"> + <MkFolder> + <template #icon><i class="ti ti-pencil"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.allNotes }}</SearchLabel></template> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['favorite', 'notes']"> + <MkFolder> + <template #icon><i class="ti ti-star"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.favoritedNotes }}</SearchLabel></template> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['clip', 'notes']"> + <MkFolder> + <template #icon><i class="ti ti-star"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.clips }}</SearchLabel></template> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['following', 'users']"> + <MkFolder> + <template #icon><i class="ti ti-users"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.followingList }}</SearchLabel></template> + <div class="_gaps_s"> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <div class="_gaps_s"> + <MkSwitch v-model="excludeMutingUsers"> + {{ i18n.ts._exportOrImport.excludeMutingUsers }} + </MkSwitch> + <MkSwitch v-model="excludeInactiveUsers"> + {{ i18n.ts._exportOrImport.excludeInactiveUsers }} + </MkSwitch> + <MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </div> + </MkFolder> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing" :defaultOpen="true"> + <template #label>{{ i18n.ts.import }}</template> + <template #icon><i class="ti ti-upload"></i></template> + <MkSwitch v-model="withReplies"> + {{ i18n.ts._exportOrImport.withReplies }} + </MkSwitch> + <MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['user', 'lists']"> + <MkFolder> + <template #icon><i class="ti ti-users"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.userLists }}</SearchLabel></template> + <div class="_gaps_s"> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists" :defaultOpen="true"> + <template #label>{{ i18n.ts.import }}</template> + <template #icon><i class="ti ti-upload"></i></template> + <MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['mute', 'users']"> + <MkFolder> + <template #icon><i class="ti ti-user-off"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.muteList }}</SearchLabel></template> + <div class="_gaps_s"> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting" :defaultOpen="true"> + <template #label>{{ i18n.ts.import }}</template> + <template #icon><i class="ti ti-upload"></i></template> + <MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['block', 'users']"> + <MkFolder> + <template #icon><i class="ti ti-user-off"></i></template> + <template #label><SearchLabel>{{ i18n.ts._exportOrImport.blockingList }}</SearchLabel></template> + <div class="_gaps_s"> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking" :defaultOpen="true"> + <template #label>{{ i18n.ts.import }}</template> + <template #icon><i class="ti ti-upload"></i></template> + <MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> + + <SearchMarker :keywords="['antennas']"> + <MkFolder> + <template #icon><i class="ti ti-antenna"></i></template> + <template #label><SearchLabel>{{ i18n.ts.antennas }}</SearchLabel></template> + <div class="_gaps_s"> + <MkFolder :defaultOpen="true"> + <template #label>{{ i18n.ts.export }}</template> + <template #icon><i class="ti ti-download"></i></template> + <MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> + </MkFolder> + <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas" :defaultOpen="true"> + <template #label>{{ i18n.ts.import }}</template> + <template #icon><i class="ti ti-upload"></i></template> + <MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> + </MkFolder> + </div> + </MkFolder> + </SearchMarker> + </div> + </div> +</SearchMarker> +</template> + +<script lang="ts" setup> +import { ref, computed } from 'vue'; +import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import * as os from '@/os.js'; +import { misskeyApi } from '@/utility/misskey-api.js'; +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'; + +const excludeMutingUsers = ref(false); +const excludeInactiveUsers = ref(false); +const withReplies = ref(store.s.defaultWithReplies); + +const onExportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.exportRequested, + }); +}; + +const onImportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.importRequested, + }); +}; + +const onError = (ev) => { + os.alert({ + type: 'error', + text: ev.message, + }); +}; + +const exportNotes = () => { + misskeyApi('i/export-notes', {}).then(onExportSuccess).catch(onError); +}; + +const exportFavorites = () => { + misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError); +}; + +const exportClips = () => { + misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError); +}; + +const exportFollowing = () => { + misskeyApi('i/export-following', { + excludeMuting: excludeMutingUsers.value, + excludeInactive: excludeInactiveUsers.value, + }) + .then(onExportSuccess).catch(onError); +}; + +const exportBlocking = () => { + misskeyApi('i/export-blocking', {}).then(onExportSuccess).catch(onError); +}; + +const exportUserLists = () => { + misskeyApi('i/export-user-lists', {}).then(onExportSuccess).catch(onError); +}; + +const exportMuting = () => { + misskeyApi('i/export-mute', {}).then(onExportSuccess).catch(onError); +}; + +const exportAntennas = () => { + misskeyApi('i/export-antennas', {}).then(onExportSuccess).catch(onError); +}; + +const importFollowing = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + misskeyApi('i/import-following', { + fileId: file.id, + withReplies: withReplies.value, + }).then(onImportSuccess).catch(onError); +}; + +const importUserLists = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + misskeyApi('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; + +const importMuting = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + misskeyApi('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; + +const importBlocking = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + misskeyApi('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; + +const importAntennas = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + misskeyApi('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; + +const headerActions = computed(() => []); + +const headerTabs = computed(() => []); + +definePage(() => ({ + title: i18n.ts._settings.accountData, + icon: 'ti ti-package', +})); +</script> + +<style module> +.button { + margin-right: 16px; +} +</style> diff --git a/packages/frontend/src/pages/settings/api.vue b/packages/frontend/src/pages/settings/api.vue deleted file mode 100644 index e41a7de0de..0000000000 --- a/packages/frontend/src/pages/settings/api.vue +++ /dev/null @@ -1,53 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<div class="_gaps_m"> - <MkButton primary @click="generateToken">{{ i18n.ts.generateAccessToken }}</MkButton> - <FormLink to="/settings/apps">{{ i18n.ts.manageAccessTokens }}</FormLink> - <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink> -</div> -</template> - -<script lang="ts" setup> -import { defineAsyncComponent, ref, computed } from 'vue'; -import FormLink from '@/components/form/link.vue'; -import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/utility/misskey-api.js'; -import { i18n } from '@/i18n.js'; -import { definePage } from '@/page.js'; - -const isDesktop = ref(window.innerWidth >= 1100); - -function generateToken() { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, { - done: async result => { - const { name, permissions } = result; - const { token } = await misskeyApi('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - - os.alert({ - type: 'success', - title: i18n.ts.token, - text: token, - }); - }, - closed: () => dispose(), - }); -} - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePage(() => ({ - title: 'API', - icon: 'ti ti-api', -})); -</script> diff --git a/packages/frontend/src/pages/settings/appearance.vue b/packages/frontend/src/pages/settings/appearance.vue index 6f8eb34d37..3fda5bc4c8 100644 --- a/packages/frontend/src/pages/settings/appearance.vue +++ b/packages/frontend/src/pages/settings/appearance.vue @@ -6,6 +6,10 @@ 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"> @@ -227,6 +231,7 @@ 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); diff --git a/packages/frontend/src/pages/settings/connect.vue b/packages/frontend/src/pages/settings/connect.vue new file mode 100644 index 0000000000..0dcd9062f0 --- /dev/null +++ b/packages/frontend/src/pages/settings/connect.vue @@ -0,0 +1,112 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<SearchMarker path="/settings/connect" :label="i18n.ts._settings.serviceConnection" :keywords="['app', 'service', 'connect', 'webhook', 'api', 'token']" icon="ti ti-link"> + <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/link_3d.png" color="#ff0088"> + <SearchKeyword>{{ i18n.ts._settings.serviceConnectionBanner }}</SearchKeyword> + </MkFeatureBanner> + + <SearchMarker :keywords="['api', 'app', 'token', 'accessToken']"> + <FormSection> + <template #label><i class="ti ti-api"></i> <SearchLabel>{{ i18n.ts._settings.api }}</SearchLabel></template> + + <div class="_gaps_m"> + <MkButton primary @click="generateToken">{{ i18n.ts.generateAccessToken }}</MkButton> + <FormLink to="/settings/apps">{{ i18n.ts.manageAccessTokens }}</FormLink> + <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink> + </div> + </FormSection> + </SearchMarker> + + <SearchMarker :keywords="['webhook']"> + <FormSection> + <template #label><i class="ti ti-webhook"></i> <SearchLabel>{{ i18n.ts._settings.webhook }}</SearchLabel></template> + + <div class="_gaps_m"> + <FormLink :to="`/settings/webhook/new`"> + {{ i18n.ts._webhookSettings.createWebhook }} + </FormLink> + + <MkFolder :defaultOpen="true"> + <template #label><SearchLabel>{{ i18n.ts.manage }}</SearchLabel></template> + + <MkPagination :pagination="pagination"> + <template #default="{items}"> + <div class="_gaps"> + <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`"> + <template #icon> + <i v-if="webhook.active === false" class="ti ti-player-pause"></i> + <i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i> + <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--MI_THEME-success)' }"></i> + <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i> + </template> + {{ webhook.name || webhook.url }} + <template #suffix> + <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime> + </template> + </FormLink> + </div> + </template> + </MkPagination> + </MkFolder> + </div> + </FormSection> + </SearchMarker> + </div> +</SearchMarker> +</template> + +<script lang="ts" setup> +import { computed, ref, defineAsyncComponent } from 'vue'; +import MkPagination from '@/components/MkPagination.vue'; +import FormSection from '@/components/form/section.vue'; +import FormLink from '@/components/form/link.vue'; +import { definePage } from '@/page.js'; +import { i18n } from '@/i18n.js'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; +import * as os from '@/os.js'; +import { misskeyApi } from '@/utility/misskey-api.js'; +import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; + +const isDesktop = ref(window.innerWidth >= 1100); + +const pagination = { + endpoint: 'i/webhooks/list' as const, + limit: 100, + noPaging: true, +}; + +function generateToken() { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, { + done: async result => { + const { name, permissions } = result; + const { token } = await misskeyApi('miauth/gen-token', { + session: null, + name: name, + permission: permissions, + }); + + os.alert({ + type: 'success', + title: i18n.ts.token, + text: token, + }); + }, + closed: () => dispose(), + }); +} + +const headerActions = computed(() => []); + +const headerTabs = computed(() => []); + +definePage(() => ({ + title: i18n.ts._settings.serviceConnection, + icon: 'ti ti-link', +})); +</script> diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index 8cc70f177f..34941d5af0 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/drive" :label="i18n.ts.drive" :keywords="['drive']" icon="ti ti-cloud"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/cloud_3d.png" color="#0059ff"> + <SearchKeyword>{{ i18n.ts._settings.driveBanner }}</SearchKeyword> + </MkFeatureBanner> + <SearchMarker :keywords="['capacity', 'usage']"> <FormSection first> <template #label><SearchLabel>{{ i18n.ts.usageAmount }}</SearchLabel></template> @@ -103,6 +107,7 @@ import { definePage } from '@/page.js'; import { signinRequired } from '@/account.js'; import { prefer } from '@/preferences.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue deleted file mode 100644 index ad5529f1e7..0000000000 --- a/packages/frontend/src/pages/settings/import-export.vue +++ /dev/null @@ -1,263 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<SearchMarker path="/settings/import-export" :label="i18n.ts.importAndExport" :keywords="['import', 'export', 'data']" icon="ti ti-package"> - <div class="_gaps_m"> - <SearchMarker :keywords="['notes']"> - <FormSection first> - <template #label><i class="ti ti-pencil"></i> <SearchLabel>{{ i18n.ts._exportOrImport.allNotes }}</SearchLabel></template> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['favorite', 'notes']"> - <FormSection> - <template #label><i class="ti ti-star"></i> <SearchLabel>{{ i18n.ts._exportOrImport.favoritedNotes }}</SearchLabel></template> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['clip', 'notes']"> - <FormSection> - <template #label><i class="ti ti-star"></i> <SearchLabel>{{ i18n.ts._exportOrImport.clips }}</SearchLabel></template> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['following', 'users']"> - <FormSection> - <template #label><i class="ti ti-users"></i> <SearchLabel>{{ i18n.ts._exportOrImport.followingList }}</SearchLabel></template> - <div class="_gaps_s"> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <div class="_gaps_s"> - <MkSwitch v-model="excludeMutingUsers"> - {{ i18n.ts._exportOrImport.excludeMutingUsers }} - </MkSwitch> - <MkSwitch v-model="excludeInactiveUsers"> - {{ i18n.ts._exportOrImport.excludeInactiveUsers }} - </MkSwitch> - <MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </div> - </MkFolder> - <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing"> - <template #label>{{ i18n.ts.import }}</template> - <template #icon><i class="ti ti-upload"></i></template> - <MkSwitch v-model="withReplies"> - {{ i18n.ts._exportOrImport.withReplies }} - </MkSwitch> - <MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> - </MkFolder> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['user', 'lists']"> - <FormSection> - <template #label><i class="ti ti-users"></i> <SearchLabel>{{ i18n.ts._exportOrImport.userLists }}</SearchLabel></template> - <div class="_gaps_s"> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists"> - <template #label>{{ i18n.ts.import }}</template> - <template #icon><i class="ti ti-upload"></i></template> - <MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> - </MkFolder> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['mute', 'users']"> - <FormSection> - <template #label><i class="ti ti-user-off"></i> <SearchLabel>{{ i18n.ts._exportOrImport.muteList }}</SearchLabel></template> - <div class="_gaps_s"> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting"> - <template #label>{{ i18n.ts.import }}</template> - <template #icon><i class="ti ti-upload"></i></template> - <MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> - </MkFolder> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['block', 'users']"> - <FormSection> - <template #label><i class="ti ti-user-off"></i> <SearchLabel>{{ i18n.ts._exportOrImport.blockingList }}</SearchLabel></template> - <div class="_gaps_s"> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking"> - <template #label>{{ i18n.ts.import }}</template> - <template #icon><i class="ti ti-upload"></i></template> - <MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> - </MkFolder> - </div> - </FormSection> - </SearchMarker> - - <SearchMarker :keywords="['antennas']"> - <FormSection> - <template #label><i class="ti ti-antenna"></i> <SearchLabel>{{ i18n.ts.antennas }}</SearchLabel></template> - <div class="_gaps_s"> - <MkFolder> - <template #label>{{ i18n.ts.export }}</template> - <template #icon><i class="ti ti-download"></i></template> - <MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton> - </MkFolder> - <MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas"> - <template #label>{{ i18n.ts.import }}</template> - <template #icon><i class="ti ti-upload"></i></template> - <MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton> - </MkFolder> - </div> - </FormSection> - </SearchMarker> - </div> -</SearchMarker> -</template> - -<script lang="ts" setup> -import { ref, computed } from 'vue'; -import MkButton from '@/components/MkButton.vue'; -import FormSection from '@/components/form/section.vue'; -import MkFolder from '@/components/MkFolder.vue'; -import MkSwitch from '@/components/MkSwitch.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/utility/misskey-api.js'; -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'; - -const excludeMutingUsers = ref(false); -const excludeInactiveUsers = ref(false); -const withReplies = ref(store.s.defaultWithReplies); - -const onExportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.exportRequested, - }); -}; - -const onImportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.importRequested, - }); -}; - -const onError = (ev) => { - os.alert({ - type: 'error', - text: ev.message, - }); -}; - -const exportNotes = () => { - misskeyApi('i/export-notes', {}).then(onExportSuccess).catch(onError); -}; - -const exportFavorites = () => { - misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError); -}; - -const exportClips = () => { - misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError); -}; - -const exportFollowing = () => { - misskeyApi('i/export-following', { - excludeMuting: excludeMutingUsers.value, - excludeInactive: excludeInactiveUsers.value, - }) - .then(onExportSuccess).catch(onError); -}; - -const exportBlocking = () => { - misskeyApi('i/export-blocking', {}).then(onExportSuccess).catch(onError); -}; - -const exportUserLists = () => { - misskeyApi('i/export-user-lists', {}).then(onExportSuccess).catch(onError); -}; - -const exportMuting = () => { - misskeyApi('i/export-mute', {}).then(onExportSuccess).catch(onError); -}; - -const exportAntennas = () => { - misskeyApi('i/export-antennas', {}).then(onExportSuccess).catch(onError); -}; - -const importFollowing = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - misskeyApi('i/import-following', { - fileId: file.id, - withReplies: withReplies.value, - }).then(onImportSuccess).catch(onError); -}; - -const importUserLists = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - misskeyApi('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); -}; - -const importMuting = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - misskeyApi('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); -}; - -const importBlocking = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - misskeyApi('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); -}; - -const importAntennas = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - misskeyApi('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError); -}; - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePage(() => ({ - title: i18n.ts.importAndExport, - icon: 'ti ti-package', -})); -</script> - -<style module> -.button { - margin-right: 16px; -} -</style> diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index d93fb176b5..26677a188f 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -156,20 +156,15 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ to: '/settings/mute-block', active: currentPage.value?.route.name === 'mute-block', }, { - icon: 'ti ti-api', - text: 'API', - to: '/settings/api', - active: currentPage.value?.route.name === 'api', - }, { - icon: 'ti ti-webhook', - text: 'Webhook', - to: '/settings/webhook', - active: currentPage.value?.route.name === 'webhook', + icon: 'ti ti-link', + text: i18n.ts._settings.serviceConnection, + to: '/settings/connect', + active: currentPage.value?.route.name === 'connect', }, { icon: 'ti ti-package', - text: i18n.ts.importAndExport, - to: '/settings/import-export', - active: currentPage.value?.route.name === 'import-export', + text: i18n.ts._settings.accountData, + to: '/settings/account-data', + active: currentPage.value?.route.name === 'account-data', }, { icon: 'ti ti-dots', text: i18n.ts.other, diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index d9c190f546..a5ab7caf99 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -6,167 +6,173 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/mute-block" :label="i18n.ts.muteAndBlock" icon="ti ti-ban" :keywords="['mute', 'block']"> <div class="_gaps_m"> - <SearchMarker - :label="i18n.ts.wordMute" - :keywords="['note', 'word', 'soft', 'mute', 'hide']" - > - <MkFolder> - <template #icon><i class="ti ti-message-off"></i></template> - <template #label>{{ i18n.ts.wordMute }}</template> + <MkFeatureBanner icon="/client-assets/prohibited_3d.png" color="#ff2600"> + <SearchKeyword>{{ i18n.ts._settings.muteAndBlockBanner }}</SearchKeyword> + </MkFeatureBanner> - <div class="_gaps_m"> - <MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo> + <div class="_gaps_s"> + <SearchMarker + :label="i18n.ts.wordMute" + :keywords="['note', 'word', 'soft', 'mute', 'hide']" + > + <MkFolder> + <template #icon><i class="ti ti-message-off"></i></template> + <template #label>{{ i18n.ts.wordMute }}</template> - <SearchMarker - :label="i18n.ts.showMutedWord" - :keywords="['show']" - > - <MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch> - </SearchMarker> + <div class="_gaps_m"> + <MkInfo>{{ i18n.ts.wordMuteDescription }}</MkInfo> - <XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/> - </div> - </MkFolder> - </SearchMarker> + <SearchMarker + :label="i18n.ts.showMutedWord" + :keywords="['show']" + > + <MkSwitch v-model="showSoftWordMutedWord">{{ i18n.ts.showMutedWord }}</MkSwitch> + </SearchMarker> - <SearchMarker - :label="i18n.ts.hardWordMute" - :keywords="['note', 'word', 'hard', 'mute', 'hide']" - > - <MkFolder> - <template #icon><i class="ti ti-message-off"></i></template> - <template #label>{{ i18n.ts.hardWordMute }}</template> + <XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/> + </div> + </MkFolder> + </SearchMarker> - <div class="_gaps_m"> - <MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo> - <XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/> - </div> - </MkFolder> - </SearchMarker> + <SearchMarker + :label="i18n.ts.hardWordMute" + :keywords="['note', 'word', 'hard', 'mute', 'hide']" + > + <MkFolder> + <template #icon><i class="ti ti-message-off"></i></template> + <template #label>{{ i18n.ts.hardWordMute }}</template> - <SearchMarker - :label="i18n.ts.instanceMute" - :keywords="['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide']" - > - <MkFolder v-if="instance.federation !== 'none'"> - <template #icon><i class="ti ti-planet-off"></i></template> - <template #label>{{ i18n.ts.instanceMute }}</template> + <div class="_gaps_m"> + <MkInfo>{{ i18n.ts.hardWordMuteDescription }}</MkInfo> + <XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/> + </div> + </MkFolder> + </SearchMarker> - <XInstanceMute/> - </MkFolder> - </SearchMarker> + <SearchMarker + :label="i18n.ts.instanceMute" + :keywords="['note', 'server', 'instance', 'host', 'federation', 'mute', 'hide']" + > + <MkFolder v-if="instance.federation !== 'none'"> + <template #icon><i class="ti ti-planet-off"></i></template> + <template #label>{{ i18n.ts.instanceMute }}</template> - <SearchMarker - :label="`${i18n.ts.mutedUsers} (${ i18n.ts.renote })`" - :keywords="['renote', 'mute', 'hide', 'user']" - > - <MkFolder> - <template #icon><i class="ti ti-repeat-off"></i></template> - <template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template> + <XInstanceMute/> + </MkFolder> + </SearchMarker> - <MkPagination :pagination="renoteMutingPagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" class="_ghost"/> - <div>{{ i18n.ts.noUsers }}</div> - </div> - </template> + <SearchMarker + :label="`${i18n.ts.mutedUsers} (${ i18n.ts.renote })`" + :keywords="['renote', 'mute', 'hide', 'user']" + > + <MkFolder> + <template #icon><i class="ti ti-repeat-off"></i></template> + <template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template> - <template #default="{ items }"> - <div class="_gaps_s"> - <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]"> - <div :class="$style.userItemMain"> - <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)"> - <MkUserCardMini :user="item.mutee"/> - </MkA> - <button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> - <button class="_button" :class="$style.remove" @click="unrenoteMute(item.mutee, $event)"><i class="ti ti-x"></i></button> - </div> - <div v-if="expandedRenoteMuteItems.includes(item.id)" :class="$style.userItemSub"> - <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div> + <MkPagination :pagination="renoteMutingPagination"> + <template #empty> + <div class="_fullinfo"> + <img :src="infoImageUrl" class="_ghost"/> + <div>{{ i18n.ts.noUsers }}</div> + </div> + </template> + + <template #default="{ items }"> + <div class="_gaps_s"> + <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]"> + <div :class="$style.userItemMain"> + <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)"> + <MkUserCardMini :user="item.mutee"/> + </MkA> + <button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> + <button class="_button" :class="$style.remove" @click="unrenoteMute(item.mutee, $event)"><i class="ti ti-x"></i></button> + </div> + <div v-if="expandedRenoteMuteItems.includes(item.id)" :class="$style.userItemSub"> + <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div> + </div> </div> </div> - </div> - </template> - </MkPagination> - </MkFolder> - </SearchMarker> + </template> + </MkPagination> + </MkFolder> + </SearchMarker> - <SearchMarker - :label="i18n.ts.mutedUsers" - :keywords="['note', 'mute', 'hide', 'user']" - > - <MkFolder> - <template #icon><i class="ti ti-eye-off"></i></template> - <template #label>{{ i18n.ts.mutedUsers }}</template> + <SearchMarker + :label="i18n.ts.mutedUsers" + :keywords="['note', 'mute', 'hide', 'user']" + > + <MkFolder> + <template #icon><i class="ti ti-eye-off"></i></template> + <template #label>{{ i18n.ts.mutedUsers }}</template> - <MkPagination :pagination="mutingPagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" class="_ghost"/> - <div>{{ i18n.ts.noUsers }}</div> - </div> - </template> + <MkPagination :pagination="mutingPagination"> + <template #empty> + <div class="_fullinfo"> + <img :src="infoImageUrl" class="_ghost"/> + <div>{{ i18n.ts.noUsers }}</div> + </div> + </template> - <template #default="{ items }"> - <div class="_gaps_s"> - <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]"> - <div :class="$style.userItemMain"> - <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)"> - <MkUserCardMini :user="item.mutee"/> - </MkA> - <button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> - <button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button> - </div> - <div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub"> - <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div> - <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div> - <div v-else>Period: {{ i18n.ts.indefinitely }}</div> + <template #default="{ items }"> + <div class="_gaps_s"> + <div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]"> + <div :class="$style.userItemMain"> + <MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)"> + <MkUserCardMini :user="item.mutee"/> + </MkA> + <button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> + <button class="_button" :class="$style.remove" @click="unmute(item.mutee, $event)"><i class="ti ti-x"></i></button> + </div> + <div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub"> + <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div> + <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div> + <div v-else>Period: {{ i18n.ts.indefinitely }}</div> + </div> </div> </div> - </div> - </template> - </MkPagination> - </MkFolder> - </SearchMarker> + </template> + </MkPagination> + </MkFolder> + </SearchMarker> - <SearchMarker - :label="i18n.ts.blockedUsers" - :keywords="['block', 'user']" - > - <MkFolder> - <template #icon><i class="ti ti-ban"></i></template> - <template #label>{{ i18n.ts.blockedUsers }}</template> + <SearchMarker + :label="i18n.ts.blockedUsers" + :keywords="['block', 'user']" + > + <MkFolder> + <template #icon><i class="ti ti-ban"></i></template> + <template #label>{{ i18n.ts.blockedUsers }}</template> - <MkPagination :pagination="blockingPagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" class="_ghost"/> - <div>{{ i18n.ts.noUsers }}</div> - </div> - </template> + <MkPagination :pagination="blockingPagination"> + <template #empty> + <div class="_fullinfo"> + <img :src="infoImageUrl" class="_ghost"/> + <div>{{ i18n.ts.noUsers }}</div> + </div> + </template> - <template #default="{ items }"> - <div class="_gaps_s"> - <div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]"> - <div :class="$style.userItemMain"> - <MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)"> - <MkUserCardMini :user="item.blockee"/> - </MkA> - <button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> - <button class="_button" :class="$style.remove" @click="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button> - </div> - <div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub"> - <div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div> - <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div> - <div v-else>Period: {{ i18n.ts.indefinitely }}</div> + <template #default="{ items }"> + <div class="_gaps_s"> + <div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]"> + <div :class="$style.userItemMain"> + <MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)"> + <MkUserCardMini :user="item.blockee"/> + </MkA> + <button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> + <button class="_button" :class="$style.remove" @click="unblock(item.blockee, $event)"><i class="ti ti-x"></i></button> + </div> + <div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub"> + <div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div> + <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div> + <div v-else>Period: {{ i18n.ts.indefinitely }}</div> + </div> </div> </div> - </div> - </template> - </MkPagination> - </MkFolder> - </SearchMarker> + </template> + </MkPagination> + </MkFolder> + </SearchMarker> + </div> </div> </SearchMarker> </template> @@ -188,6 +194,7 @@ import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import { reloadAsk } from '@/utility/reload-ask.js'; import { prefer } from '@/preferences.js'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index ca0de0b4b1..49910cdf4a 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -5,6 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/bell_3d.png" color="#ffff00"> + <SearchKeyword>{{ i18n.ts._settings.notificationsBanner }}</SearchKeyword> + </MkFeatureBanner> + <FormSection first> <template #label>{{ i18n.ts.notificationRecieveConfig }}</template> <div class="_gaps_s"> @@ -63,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { shallowRef, computed } from 'vue'; +import { notificationTypes } from '@@/js/const.js'; import XNotificationConfig from './notifications.notification-config.vue'; import type { NotificationConfig } from './notifications.notification-config.vue'; import FormLink from '@/components/form/link.vue'; @@ -75,7 +80,7 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue'; -import { notificationTypes } from '@@/js/const.js'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 93a0e8a850..16d5947ad2 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -4,8 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<SearchMarker path="/settings/plugin" :label="i18n.ts.plugins" :keywords="['plugin']" icon="ti ti-plug"> +<SearchMarker path="/settings/plugin" :label="i18n.ts.plugins" :keywords="['plugin', 'addon', 'extension']" icon="ti ti-plug"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/electric_plug_3d.png" color="#ffbb00"> + <SearchKeyword>{{ i18n.ts._settings.pluginBanner }}</SearchKeyword> + </MkFeatureBanner> + <FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink> <FormSection> @@ -98,6 +102,7 @@ import MkButton from '@/components/MkButton.vue'; import MkCode from '@/components/MkCode.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin, reloadPlugin } from '@/plugin.js'; diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 58e01df633..374477c510 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/preferences" :label="i18n.ts.preferences" :keywords="['general', 'preferences']" icon="ti ti-adjustments"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/gear_3d.png" color="#00ff9d"> + <SearchKeyword>{{ i18n.ts._settings.preferencesBanner }}</SearchKeyword> + </MkFeatureBanner> + <SearchMarker :keywords="['language']"> <MkSelect v-model="lang"> <template #label><SearchLabel>{{ i18n.ts.uiLanguage }}</SearchLabel></template> @@ -381,6 +385,7 @@ import { definePage } from '@/page.js'; import { miLocalStorage } from '@/local-storage.js'; import { prefer } from '@/preferences.js'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const lang = ref(miLocalStorage.getItem('lang')); const dataSaver = ref(prefer.s.dataSaver); diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index d42dd323e0..edc750c295 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/privacy" :label="i18n.ts.privacy" :keywords="['privacy']" icon="ti ti-lock-open"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/unlocked_3d.png" color="#aeff00"> + <SearchKeyword>{{ i18n.ts._settings.privacyBanner }}</SearchKeyword> + </MkFeatureBanner> + <SearchMarker :keywords="['follow', 'lock']"> <MkSwitch v-model="isLocked" @update:modelValue="save()"> <template #label><SearchLabel>{{ i18n.ts.makeFollowManuallyApprove }}</SearchLabel></template> @@ -189,6 +193,7 @@ import MkInput from '@/components/MkInput.vue'; import * as os from '@/os.js'; import MkDisableSection from '@/components/MkDisableSection.vue'; import MkInfo from '@/components/MkInfo.vue'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const $i = signinRequired(); diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue index 9b664fa98a..391118effd 100644 --- a/packages/frontend/src/pages/settings/security.vue +++ b/packages/frontend/src/pages/settings/security.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/security" :label="i18n.ts.security" :keywords="['security']" icon="ti ti-lock" :inlining="['2fa']"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/locked_with_key_3d.png" color="#ffbf00"> + <SearchKeyword>{{ i18n.ts._settings.securityBanner }}</SearchKeyword> + </MkFeatureBanner> + <SearchMarker :keywords="['password']"> <FormSection first> <template #label><SearchLabel>{{ i18n.ts.password }}</SearchLabel></template> @@ -59,6 +63,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const pagination = { endpoint: 'i/signin-history' as const, diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index 0c447b1a67..9e5c82a266 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <SearchMarker path="/settings/sounds" :label="i18n.ts.sounds" :keywords="['sounds']" icon="ti ti-music"> <div class="_gaps_m"> + <MkFeatureBanner icon="/client-assets/speaker_high_volume_3d.png" color="#ff006f"> + <SearchKeyword>{{ i18n.ts._settings.soundsBanner }}</SearchKeyword> + </MkFeatureBanner> + <SearchMarker :keywords="['mute']"> <MkPreferenceContainer k="sound.notUseSound"> <MkSwitch v-model="notUseSound"> @@ -70,6 +74,7 @@ import { operationTypes } from '@/utility/sound.js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; import { PREF_DEF } from '@/preferences/def.js'; +import MkFeatureBanner from '@/components/MkFeatureBanner.vue'; const notUseSound = prefer.model('sound.notUseSound'); const useSoundOnlyWhenActive = prefer.model('sound.useSoundOnlyWhenActive'); diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue deleted file mode 100644 index bf8af8cdce..0000000000 --- a/packages/frontend/src/pages/settings/webhook.vue +++ /dev/null @@ -1,57 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<div class="_gaps_m"> - <FormLink :to="`/settings/webhook/new`"> - {{ i18n.ts._webhookSettings.createWebhook }} - </FormLink> - - <FormSection> - <MkPagination :pagination="pagination"> - <template #default="{items}"> - <div class="_gaps"> - <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`"> - <template #icon> - <i v-if="webhook.active === false" class="ti ti-player-pause"></i> - <i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i> - <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--MI_THEME-success)' }"></i> - <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i> - </template> - {{ webhook.name || webhook.url }} - <template #suffix> - <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime> - </template> - </FormLink> - </div> - </template> - </MkPagination> - </FormSection> -</div> -</template> - -<script lang="ts" setup> -import { computed } from 'vue'; -import MkPagination from '@/components/MkPagination.vue'; -import FormSection from '@/components/form/section.vue'; -import FormLink from '@/components/form/link.vue'; -import { definePage } from '@/page.js'; -import { i18n } from '@/i18n.js'; - -const pagination = { - endpoint: 'i/webhooks/list' as const, - limit: 100, - noPaging: true, -}; - -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - -definePage(() => ({ - title: 'Webhook', - icon: 'ti ti-webhook', -})); -</script> |