diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f353654d17..d230f15a7f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -409,6 +409,7 @@ remote: "リモート" total: "合計" weekOverWeekChanges: "前週比" dayOverDayChanges: "前日比" +accessibility: "アクセシビリティ" _ago: unknown: "謎" diff --git a/src/client/app.vue b/src/client/app.vue index ecf437cacb..82e118a8b6 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -87,6 +87,9 @@ <fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span> <i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i> </button> + <router-link class="item" active-class="active" to="/settings"> + <fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span> + </router-link> </div> </nav> </transition> @@ -881,6 +884,7 @@ export default Vue.extend({ width: $nav-width; height: 100vh; padding: 16px 0; + padding-bottom: calc(3.7rem + 24px); box-sizing: border-box; overflow: auto; background: var(--navBg); @@ -894,6 +898,7 @@ export default Vue.extend({ @media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) { width: $nav-icon-only-width; padding: 8px 0; + padding-bottom: calc(3.7rem + 24px); > .divider { margin: 8px auto; @@ -948,6 +953,17 @@ export default Vue.extend({ color: var(--navActive); } + &:last-child { + position: fixed; + bottom: 0; + width: inherit; + padding-top: 8px; + padding-bottom: 8px; + background: var(--navBg); + border-top: solid 1px var(--divider); + border-right: solid 1px var(--divider); + } + @media (max-width: $nav-icon-only-threshold) and (min-width: $nav-hide-threshold + 1px) { padding-left: 0; width: 100%; diff --git a/src/client/pages/settings/2fa.vue b/src/client/pages/my-settings/2fa.vue similarity index 100% rename from src/client/pages/settings/2fa.vue rename to src/client/pages/my-settings/2fa.vue diff --git a/src/client/pages/settings/api.vue b/src/client/pages/my-settings/api.vue similarity index 100% rename from src/client/pages/settings/api.vue rename to src/client/pages/my-settings/api.vue diff --git a/src/client/pages/settings/drive.vue b/src/client/pages/my-settings/drive.vue similarity index 100% rename from src/client/pages/settings/drive.vue rename to src/client/pages/my-settings/drive.vue diff --git a/src/client/pages/settings/import-export.vue b/src/client/pages/my-settings/import-export.vue similarity index 100% rename from src/client/pages/settings/import-export.vue rename to src/client/pages/my-settings/import-export.vue diff --git a/src/client/pages/my-settings/index.vue b/src/client/pages/my-settings/index.vue new file mode 100644 index 0000000000..53f08785f2 --- /dev/null +++ b/src/client/pages/my-settings/index.vue @@ -0,0 +1,100 @@ +<template> +<div> + <portal to="icon"><fa :icon="faCog"/></portal> + <portal to="title">{{ $t('settings') }}</portal> + + <x-profile-setting/> + <x-privacy-setting/> + <x-reaction-setting/> + + <section class="_card"> + <div class="_title"><fa :icon="faCog"/> {{ $t('general') }}</div> + <div class="_content"> + <mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch"> + {{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</template> + </mk-switch> + </div> + <div class="_content"> + <mk-button @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</mk-button> + <mk-button @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</mk-button> + <mk-button @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</mk-button> + </div> + </section> + + <x-import-export/> + <x-drive/> + <x-mute-block/> + <x-security/> + <x-2fa/> + <x-integration/> + <x-api/> + + <mk-button @click="$root.signout()" primary style="margin: var(--margin) auto;">{{ $t('logout') }}</mk-button> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { faCog } from '@fortawesome/free-solid-svg-icons'; +import XProfileSetting from './profile.vue'; +import XPrivacySetting from './privacy.vue'; +import XImportExport from './import-export.vue'; +import XDrive from './drive.vue'; +import XReactionSetting from './reaction.vue'; +import XMuteBlock from './mute-block.vue'; +import XSecurity from './security.vue'; +import X2fa from './2fa.vue'; +import XIntegration from './integration.vue'; +import XApi from './api.vue'; +import MkButton from '../../components/ui/button.vue'; +import MkSwitch from '../../components/ui/switch.vue'; + +export default Vue.extend({ + metaInfo() { + return { + title: this.$t('settings') as string + }; + }, + + components: { + XProfileSetting, + XPrivacySetting, + XImportExport, + XDrive, + XReactionSetting, + XMuteBlock, + XSecurity, + X2fa, + XIntegration, + XApi, + MkButton, + MkSwitch, + }, + + data() { + return { + faCog + } + }, + + methods: { + onChangeAutoWatch(v) { + this.$root.api('i/update', { + autoWatch: v + }); + }, + + readAllUnreadNotes() { + this.$root.api('i/read_all_unread_notes'); + }, + + readAllMessagingMessages() { + this.$root.api('i/read_all_messaging_messages'); + }, + + readAllNotifications() { + this.$root.api('notifications/mark_all_as_read'); + }, + } +}); +</script> diff --git a/src/client/pages/settings/integration.vue b/src/client/pages/my-settings/integration.vue similarity index 100% rename from src/client/pages/settings/integration.vue rename to src/client/pages/my-settings/integration.vue diff --git a/src/client/pages/settings/mute-block.vue b/src/client/pages/my-settings/mute-block.vue similarity index 100% rename from src/client/pages/settings/mute-block.vue rename to src/client/pages/my-settings/mute-block.vue diff --git a/src/client/pages/settings/privacy.vue b/src/client/pages/my-settings/privacy.vue similarity index 100% rename from src/client/pages/settings/privacy.vue rename to src/client/pages/my-settings/privacy.vue diff --git a/src/client/pages/settings/profile.vue b/src/client/pages/my-settings/profile.vue similarity index 100% rename from src/client/pages/settings/profile.vue rename to src/client/pages/my-settings/profile.vue diff --git a/src/client/pages/settings/reaction.vue b/src/client/pages/my-settings/reaction.vue similarity index 100% rename from src/client/pages/settings/reaction.vue rename to src/client/pages/my-settings/reaction.vue diff --git a/src/client/pages/settings/security.vue b/src/client/pages/my-settings/security.vue similarity index 100% rename from src/client/pages/settings/security.vue rename to src/client/pages/my-settings/security.vue diff --git a/src/client/pages/settings/general.vue b/src/client/pages/settings/general.vue deleted file mode 100644 index 5a176c0226..0000000000 --- a/src/client/pages/settings/general.vue +++ /dev/null @@ -1,159 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faCog"/> {{ $t('general') }}</div> - <div class="_content"> - <mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button> - <mk-button primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</mk-button> - </div> - <div class="_content"> - <mk-switch v-model="autoReload"> - {{ $t('autoReloadWhenDisconnected') }} - </mk-switch> - <mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch"> - {{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</template> - </mk-switch> - </div> - <div class="_content"> - <mk-button @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</mk-button> - <mk-button @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</mk-button> - <mk-button @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</mk-button> - </div> - <div class="_content"> - <mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch> - <mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch> - <mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch> - <mk-switch v-model="useOsNativeEmojis"> - {{ $t('useOsNativeEmojis') }} - <template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template> - </mk-switch> - </div> - <div class="_content"> - <mk-select v-model="lang"> - <template #label>{{ $t('uiLanguage') }}</template> - - <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> - </mk-select> - </div> - <div class="_content"> - <div>{{ $t('fontSize') }}</div> - <mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio> - <mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio> - <mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio> - <mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faImage, faCog } from '@fortawesome/free-solid-svg-icons'; -import MkInput from '../../components/ui/input.vue'; -import MkButton from '../../components/ui/button.vue'; -import MkSwitch from '../../components/ui/switch.vue'; -import MkSelect from '../../components/ui/select.vue'; -import MkRadio from '../../components/ui/radio.vue'; -import i18n from '../../i18n'; -import { langs } from '../../config'; -import { selectFile } from '../../scripts/select-file'; - -export default Vue.extend({ - i18n, - - components: { - MkInput, - MkButton, - MkSwitch, - MkSelect, - MkRadio, - }, - - data() { - return { - langs, - lang: localStorage.getItem('lang'), - fontSize: localStorage.getItem('fontSize'), - wallpaper: localStorage.getItem('wallpaper'), - faImage, faCog - } - }, - - computed: { - autoReload: { - get() { return this.$store.state.device.autoReload; }, - set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); } - }, - - reduceAnimation: { - get() { return !this.$store.state.device.animation; }, - set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); } - }, - - disableAnimatedMfm: { - get() { return !this.$store.state.device.animatedMfm; }, - set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); } - }, - - useOsNativeEmojis: { - get() { return this.$store.state.device.useOsNativeEmojis; }, - set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); } - }, - - imageNewTab: { - get() { return this.$store.state.device.imageNewTab; }, - set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); } - }, - }, - - watch: { - lang() { - localStorage.setItem('lang', this.lang); - localStorage.removeItem('locale'); - location.reload(); - }, - - fontSize() { - if (this.fontSize == null) { - localStorage.removeItem('fontSize'); - } else { - localStorage.setItem('fontSize', this.fontSize); - } - location.reload(); - }, - - wallpaper() { - if (this.wallpaper == null) { - localStorage.removeItem('wallpaper'); - } else { - localStorage.setItem('wallpaper', this.wallpaper); - } - location.reload(); - } - }, - - methods: { - setWallpaper(e) { - selectFile(this, e.currentTarget || e.target, null, false).then(file => { - this.wallpaper = file.url; - }); - }, - - onChangeAutoWatch(v) { - this.$root.api('i/update', { - autoWatch: v - }); - }, - - readAllUnreadNotes() { - this.$root.api('i/read_all_unread_notes'); - }, - - readAllMessagingMessages() { - this.$root.api('i/read_all_messaging_messages'); - }, - - readAllNotifications() { - this.$root.api('notifications/mark_all_as_read'); - } - } -}); -</script> diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue index aa827aa949..977a59bd8b 100644 --- a/src/client/pages/settings/index.vue +++ b/src/client/pages/settings/index.vue @@ -1,44 +1,61 @@ <template> -<div class="mk-settings-page"> +<div> <portal to="icon"><fa :icon="faCog"/></portal> <portal to="title">{{ $t('settings') }}</portal> - <x-profile-setting/> - <x-privacy-setting/> - <x-reaction-setting/> <x-theme/> - <x-import-export/> - <x-drive/> - <x-general/> - <x-mute-block/> - <x-security/> - <x-2fa/> - <x-integration/> - <x-api/> - <mk-button @click="cacheClear()" primary class="cacheClear">{{ $t('cacheClear') }}</mk-button> - <mk-button @click="$root.signout()" primary class="logout">{{ $t('logout') }}</mk-button> + <section class="_card"> + <div class="_title"><fa :icon="faCog"/> {{ $t('accessibility') }}</div> + <div class="_content"> + <mk-switch v-model="autoReload"> + {{ $t('autoReloadWhenDisconnected') }} + </mk-switch> + </div> + <div class="_content"> + <mk-switch v-model="imageNewTab">{{ $t('openImageInNewTab') }}</mk-switch> + <mk-switch v-model="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</mk-switch> + <mk-switch v-model="reduceAnimation">{{ $t('reduceUiAnimation') }}</mk-switch> + <mk-switch v-model="useOsNativeEmojis"> + {{ $t('useOsNativeEmojis') }} + <template #desc><mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template> + </mk-switch> + </div> + <div class="_content"> + <mk-select v-model="lang"> + <template #label>{{ $t('uiLanguage') }}</template> + + <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option> + </mk-select> + </div> + <div class="_content"> + <div>{{ $t('fontSize') }}</div> + <mk-radio v-model="fontSize" value="small"><span style="font-size: 14px;">Aa</span></mk-radio> + <mk-radio v-model="fontSize" :value="null"><span style="font-size: 16px;">Aa</span></mk-radio> + <mk-radio v-model="fontSize" value="large"><span style="font-size: 18px;">Aa</span></mk-radio> + <mk-radio v-model="fontSize" value="veryLarge"><span style="font-size: 20px;">Aa</span></mk-radio> + </div> + </section> + + <mk-button @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</mk-button> </div> </template> <script lang="ts"> import Vue from 'vue'; -import { faCog } from '@fortawesome/free-solid-svg-icons'; -import XProfileSetting from './profile.vue'; -import XPrivacySetting from './privacy.vue'; -import XImportExport from './import-export.vue'; -import XDrive from './drive.vue'; -import XGeneral from './general.vue'; -import XReactionSetting from './reaction.vue'; -import XMuteBlock from './mute-block.vue'; -import XSecurity from './security.vue'; -import XTheme from './theme.vue'; -import X2fa from './2fa.vue'; -import XIntegration from './integration.vue'; -import XApi from './api.vue'; +import { faImage, faCog } from '@fortawesome/free-solid-svg-icons'; +import MkInput from '../../components/ui/input.vue'; import MkButton from '../../components/ui/button.vue'; +import MkSwitch from '../../components/ui/switch.vue'; +import MkSelect from '../../components/ui/select.vue'; +import MkRadio from '../../components/ui/radio.vue'; +import XTheme from './theme.vue'; +import i18n from '../../i18n'; +import { langs } from '../../config'; export default Vue.extend({ + i18n, + metaInfo() { return { title: this.$t('settings') as string @@ -46,27 +63,67 @@ export default Vue.extend({ }, components: { - XProfileSetting, - XPrivacySetting, - XImportExport, - XDrive, - XGeneral, - XReactionSetting, - XMuteBlock, - XSecurity, XTheme, - X2fa, - XIntegration, - XApi, + MkInput, MkButton, + MkSwitch, + MkSelect, + MkRadio, }, data() { return { - faCog + langs, + lang: localStorage.getItem('lang'), + fontSize: localStorage.getItem('fontSize'), + faImage, faCog } }, + computed: { + autoReload: { + get() { return this.$store.state.device.autoReload; }, + set(value) { this.$store.commit('device/set', { key: 'autoReload', value }); } + }, + + reduceAnimation: { + get() { return !this.$store.state.device.animation; }, + set(value) { this.$store.commit('device/set', { key: 'animation', value: !value }); } + }, + + disableAnimatedMfm: { + get() { return !this.$store.state.device.animatedMfm; }, + set(value) { this.$store.commit('device/set', { key: 'animatedMfm', value: !value }); } + }, + + useOsNativeEmojis: { + get() { return this.$store.state.device.useOsNativeEmojis; }, + set(value) { this.$store.commit('device/set', { key: 'useOsNativeEmojis', value }); } + }, + + imageNewTab: { + get() { return this.$store.state.device.imageNewTab; }, + set(value) { this.$store.commit('device/set', { key: 'imageNewTab', value }); } + }, + }, + + watch: { + lang() { + localStorage.setItem('lang', this.lang); + localStorage.removeItem('locale'); + location.reload(); + }, + + fontSize() { + if (this.fontSize == null) { + localStorage.removeItem('fontSize'); + } else { + localStorage.setItem('fontSize', this.fontSize); + } + location.reload(); + }, + }, + methods: { cacheClear() { // Clear cache (service worker) @@ -86,12 +143,3 @@ export default Vue.extend({ } }); </script> - -<style lang="scss" scoped> -.mk-settings-page { - > .logout, - > .cacheClear { - margin: 8px auto; - } -} -</style> diff --git a/src/client/pages/settings/theme.vue b/src/client/pages/settings/theme.vue index e8f11fb03c..e284c8c6f5 100644 --- a/src/client/pages/settings/theme.vue +++ b/src/client/pages/settings/theme.vue @@ -12,6 +12,10 @@ </optgroup> </mk-select> </div> + <div class="_content"> + <mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button> + <mk-button primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</mk-button> + </div> </section> </template> @@ -23,6 +27,7 @@ import MkButton from '../../components/ui/button.vue'; import MkSelect from '../../components/ui/select.vue'; import i18n from '../../i18n'; import { Theme, builtinThemes, applyTheme } from '../../theme'; +import { selectFile } from '../../scripts/select-file'; export default Vue.extend({ i18n, @@ -35,6 +40,7 @@ export default Vue.extend({ data() { return { + wallpaper: localStorage.getItem('wallpaper'), faPalette } }, @@ -65,11 +71,25 @@ export default Vue.extend({ watch: { theme() { applyTheme(this.themes.find(x => x.id === this.theme)); + }, + + + wallpaper() { + if (this.wallpaper == null) { + localStorage.removeItem('wallpaper'); + } else { + localStorage.setItem('wallpaper', this.wallpaper); + } + location.reload(); } }, methods: { - + setWallpaper(e) { + selectFile(this, e.currentTarget || e.target, null, false).then(file => { + this.wallpaper = file.url; + }); + }, } }); </script> diff --git a/src/client/router.ts b/src/client/router.ts index fe3de70a05..0a856d580d 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -38,13 +38,14 @@ export const router = new VueRouter({ { path: '/my/pages', name: 'pages', component: page('pages') }, { path: '/my/pages/new', component: page('page-editor/page-editor') }, { path: '/my/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, - { path: '/my/settings', component: page('settings/index') }, + { path: '/my/settings', component: page('my-settings/index') }, { path: '/my/follow-requests', component: page('follow-requests') }, { path: '/my/lists', component: page('my-lists/index') }, { path: '/my/lists/:list', component: page('my-lists/list') }, { path: '/my/groups', component: page('my-groups/index') }, { path: '/my/groups/:group', component: page('my-groups/group') }, { path: '/my/antennas', component: page('my-antennas/index') }, + { path: '/settings', component: page('settings/index') }, { path: '/instance', component: page('instance/index') }, { path: '/instance/emojis', component: page('instance/emojis') }, { path: '/instance/users', component: page('instance/users') },