diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2020-02-17 03:10:51 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2020-02-17 03:10:51 +0900 |
| commit | 93d25a2a34df72fd8ce62cce33fa098cf11d5eb1 (patch) | |
| tree | c1a5ba7407fc0ea3053caa660af2dd514f8599b7 /src/client/pages/settings | |
| parent | :art: (diff) | |
| download | misskey-93d25a2a34df72fd8ce62cce33fa098cf11d5eb1.tar.gz misskey-93d25a2a34df72fd8ce62cce33fa098cf11d5eb1.tar.bz2 misskey-93d25a2a34df72fd8ce62cce33fa098cf11d5eb1.zip | |
ユーザー設定とクライアント設定を分離
Diffstat (limited to 'src/client/pages/settings')
| -rw-r--r-- | src/client/pages/settings/2fa.vue | 253 | ||||
| -rw-r--r-- | src/client/pages/settings/api.vue | 46 | ||||
| -rw-r--r-- | src/client/pages/settings/drive.vue | 212 | ||||
| -rw-r--r-- | src/client/pages/settings/general.vue | 159 | ||||
| -rw-r--r-- | src/client/pages/settings/import-export.vue | 121 | ||||
| -rw-r--r-- | src/client/pages/settings/index.vue | 144 | ||||
| -rw-r--r-- | src/client/pages/settings/integration.vue | 131 | ||||
| -rw-r--r-- | src/client/pages/settings/mute-block.vue | 76 | ||||
| -rw-r--r-- | src/client/pages/settings/privacy.vue | 76 | ||||
| -rw-r--r-- | src/client/pages/settings/profile.vue | 223 | ||||
| -rw-r--r-- | src/client/pages/settings/reaction.vue | 62 | ||||
| -rw-r--r-- | src/client/pages/settings/security.vue | 87 | ||||
| -rw-r--r-- | src/client/pages/settings/theme.vue | 22 |
13 files changed, 117 insertions, 1495 deletions
diff --git a/src/client/pages/settings/2fa.vue b/src/client/pages/settings/2fa.vue deleted file mode 100644 index 8432bdb32d..0000000000 --- a/src/client/pages/settings/2fa.vue +++ /dev/null @@ -1,253 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faLock"/> {{ $t('twoStepAuthentication') }}</div> - <div class="_content"> - <p v-if="!data && !$store.state.i.twoFactorEnabled"><mk-button @click="register">{{ $t('_2fa.registerDevice') }}</mk-button></p> - <template v-if="$store.state.i.twoFactorEnabled"> - <p>{{ $t('_2fa.alreadyRegistered') }}</p> - <mk-button @click="unregister">{{ $t('unregister') }}</mk-button> - - <template v-if="supportsCredentials"> - <hr class="totp-method-sep"> - - <h2 class="heading">{{ $t('securityKey') }}</h2> - <p>{{ $t('_2fa.securityKeyInfo') }}</p> - <div class="key-list"> - <div class="key" v-for="key in $store.state.i.securityKeysList"> - <h3>{{ key.name }}</h3> - <div class="last-used">{{ $t('lastUsed') }}<mk-time :time="key.lastUsed"/></div> - <mk-button @click="unregisterKey(key)">{{ $t('unregister') }}</mk-button> - </div> - </div> - - <mk-switch v-model="usePasswordLessLogin" @change="updatePasswordLessLogin" v-if="$store.state.i.securityKeysList.length > 0">{{ $t('passwordLessLogin') }}</mk-switch> - - <mk-info warn v-if="registration && registration.error">{{ $t('something-went-wrong') }} {{ registration.error }}</mk-info> - <mk-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('_2fa.registerKey') }}</mk-button> - - <ol v-if="registration && !registration.error"> - <li v-if="registration.stage >= 0"> - {{ $t('activate-key') }} - <fa icon="spinner" pulse fixed-width v-if="registration.saving && registration.stage == 0" /> - </li> - <li v-if="registration.stage >= 1"> - <mk-form :disabled="registration.stage != 1 || registration.saving"> - <mk-input v-model="keyName" :max="30"> - <span>{{ $t('securityKeyName') }}</span> - </mk-input> - <mk-button @click="registerKey" :disabled="keyName.length == 0">{{ $t('registerSecurityKey') }}</mk-button> - <fa icon="spinner" pulse fixed-width v-if="registration.saving && registration.stage == 1" /> - </mk-form> - </li> - </ol> - </template> - </template> - <div v-if="data && !$store.state.i.twoFactorEnabled"> - <ol style="margin: 0; padding: 0 0 0 1em;"> - <li> - <i18n path="_2fa.step1" tag="span"> - <a href="https://authy.com/" rel="noopener" target="_blank" place="a" class="_link">Authy</a> - <a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" place="b" class="_link">Google Authenticator</a> - </i18n> - </li> - <li>{{ $t('_2fa.step2') }}<br><img :src="data.qr"></li> - <li>{{ $t('_2fa.step3') }}<br> - <mk-input v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false">{{ $t('token') }}</mk-input> - <mk-button primary @click="submit">{{ $t('done') }}</mk-button> - </li> - </ol> - <mk-info>{{ $t('_2fa.step4') }}</mk-info> - </div> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faLock } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; -import { hostname } from '../../config'; -import { hexifyAB } from '../../scripts/2fa'; -import MkButton from '../../components/ui/button.vue'; -import MkInfo from '../../components/ui/info.vue'; -import MkInput from '../../components/ui/input.vue'; -import MkSwitch from '../../components/ui/switch.vue'; - -function stringifyAB(buffer) { - return String.fromCharCode.apply(null, new Uint8Array(buffer)); -} - -export default Vue.extend({ - i18n, - components: { - MkButton, MkInfo, MkInput, MkSwitch - }, - data() { - return { - data: null, - supportsCredentials: !!navigator.credentials, - usePasswordLessLogin: this.$store.state.i.usePasswordLessLogin, - registration: null, - keyName: '', - token: null, - faLock - }; - }, - methods: { - register() { - this.$root.dialog({ - title: this.$t('password'), - input: { - type: 'password' - } - }).then(({ canceled, result: password }) => { - if (canceled) return; - this.$root.api('i/2fa/register', { - password: password - }).then(data => { - this.data = data; - }); - }); - }, - - unregister() { - this.$root.dialog({ - title: this.$t('password'), - input: { - type: 'password' - } - }).then(({ canceled, result: password }) => { - if (canceled) return; - this.$root.api('i/2fa/unregister', { - password: password - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - this.$root.dialog({ - type: 'success', - iconOnly: true, autoClose: true - }); - this.$store.state.i.twoFactorEnabled = false; - }); - }); - }, - - submit() { - this.$root.api('i/2fa/done', { - token: this.token - }).then(() => { - this.$root.dialog({ - type: 'success', - iconOnly: true, autoClose: true - }); - this.$store.state.i.twoFactorEnabled = true; - }).catch(e => { - this.$root.dialog({ - type: 'error', - iconOnly: true, autoClose: true - }); - }); - }, - - registerKey() { - this.registration.saving = true; - this.$root.api('i/2fa/key-done', { - password: this.registration.password, - name: this.keyName, - challengeId: this.registration.challengeId, - // we convert each 16 bits to a string to serialise - clientDataJSON: stringifyAB(this.registration.credential.response.clientDataJSON), - attestationObject: hexifyAB(this.registration.credential.response.attestationObject) - }).then(key => { - this.registration = null; - key.lastUsed = new Date(); - this.$root.dialog({ - type: 'success', - iconOnly: true, autoClose: true - }); - }) - }, - - unregisterKey(key) { - this.$root.dialog({ - title: this.$t('password'), - input: { - type: 'password' - } - }).then(({ canceled, result: password }) => { - if (canceled) return; - return this.$root.api('i/2fa/remove-key', { - password, - credentialId: key.id - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - this.$root.dialog({ - type: 'success', - iconOnly: true, autoClose: true - }); - }); - }); - }, - - addSecurityKey() { - this.$root.dialog({ - title: this.$t('password'), - input: { - type: 'password' - } - }).then(({ canceled, result: password }) => { - if (canceled) return; - this.$root.api('i/2fa/register-key', { - password - }).then(registration => { - this.registration = { - password, - challengeId: registration.challengeId, - stage: 0, - publicKeyOptions: { - challenge: Buffer.from( - registration.challenge - .replace(/\-/g, "+") - .replace(/_/g, "/"), - 'base64' - ), - rp: { - id: hostname, - name: 'Misskey' - }, - user: { - id: Uint8Array.from(this.$store.state.i.id, c => c.charCodeAt(0)), - name: this.$store.state.i.username, - displayName: this.$store.state.i.name, - }, - pubKeyCredParams: [{alg: -7, type: 'public-key'}], - timeout: 60000, - attestation: 'direct' - }, - saving: true - }; - return navigator.credentials.create({ - publicKey: this.registration.publicKeyOptions - }); - }).then(credential => { - this.registration.credential = credential; - this.registration.saving = false; - this.registration.stage = 1; - }).catch(err => { - console.warn('Error while registering?', err); - this.registration.error = err.message; - this.registration.stage = -1; - }); - }); - }, - updatePasswordLessLogin() { - this.$root.api('i/2fa/password-less', { - value: !!this.usePasswordLessLogin - }); - } - } -}); -</script> diff --git a/src/client/pages/settings/api.vue b/src/client/pages/settings/api.vue deleted file mode 100644 index f394c826de..0000000000 --- a/src/client/pages/settings/api.vue +++ /dev/null @@ -1,46 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faKey"/> API</div> - <div class="_content"> - <mk-input :value="$store.state.i.token" readonly> - <span>{{ $t('token') }}</span> - </mk-input> - <mk-button @click="regenerateToken"><fa :icon="faSyncAlt"/> {{ $t('regenerate') }}</mk-button> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faKey, faSyncAlt } from '@fortawesome/free-solid-svg-icons'; -import i18n from '../../i18n'; -import MkButton from '../../components/ui/button.vue'; -import MkInput from '../../components/ui/input.vue'; - -export default Vue.extend({ - i18n, - components: { - MkButton, MkInput - }, - data() { - return { - faKey, faSyncAlt - }; - }, - methods: { - regenerateToken() { - this.$root.dialog({ - title: this.$t('password'), - input: { - type: 'password' - } - }).then(({ canceled, result: password }) => { - if (canceled) return; - this.$root.api('i/regenerate_token', { - password: password - }); - }); - }, - } -}); -</script> diff --git a/src/client/pages/settings/drive.vue b/src/client/pages/settings/drive.vue deleted file mode 100644 index 55372687fc..0000000000 --- a/src/client/pages/settings/drive.vue +++ /dev/null @@ -1,212 +0,0 @@ -<template> -<section class="uawsfosz _card"> - <div class="_title"><fa :icon="faCloud"/> {{ $t('drive') }}</div> - <div class="_content"> - <mk-pagination :pagination="drivePagination" #default="{items}" class="drive" ref="drive"> - <div class="file" v-for="(file, i) in items" :key="file.id" @click="selected = file" :class="{ selected: selected && (selected.id === file.id) }"> - <x-file-thumbnail class="thumbnail" :file="file" fit="cover"/> - <div class="body"> - <p class="name"> - <span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span> - <span class="ext" v-if="file.name.lastIndexOf('.') != -1">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span> - </p> - <footer> - <span class="type"><x-file-type-icon :type="file.type" class="icon"/>{{ file.type }}</span> - <span class="separator"></span> - <span class="data-size">{{ file.size | bytes }}</span> - <span class="separator"></span> - <span class="created-at"><fa :icon="faClock"/><mk-time :time="file.createdAt"/></span> - <template v-if="file.isSensitive"> - <span class="separator"></span> - <span class="nsfw"><fa :icon="faEyeSlash"/> {{ $t('nsfw') }}</span> - </template> - </footer> - </div> - </div> - </mk-pagination> - </div> - <div class="_footer"> - <mk-button primary inline :disabled="selected == null" @click="download()"><fa :icon="faDownload"/> {{ $t('download') }}</mk-button> - <mk-button inline :disabled="selected == null" @click="del()"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</mk-button> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faCloud, faDownload } from '@fortawesome/free-solid-svg-icons'; -import { faClock, faEyeSlash, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; -import XFileTypeIcon from '../../components/file-type-icon.vue'; -import XFileThumbnail from '../../components/drive-file-thumbnail.vue'; -import MkButton from '../../components/ui/button.vue'; -import MkPagination from '../../components/ui/pagination.vue'; -import i18n from '../../i18n'; - -export default Vue.extend({ - i18n, - - components: { - XFileTypeIcon, - XFileThumbnail, - MkPagination, - MkButton, - }, - - data() { - return { - selected: null, - connection: null, - drivePagination: { - endpoint: 'drive/files', - limit: 10, - }, - faCloud, faClock, faEyeSlash, faDownload, faTrashAlt - } - }, - - created() { - this.connection = this.$root.stream.useSharedConnection('drive'); - - this.connection.on('fileCreated', this.onStreamDriveFileCreated); - this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); - this.connection.on('fileDeleted', this.onStreamDriveFileDeleted); - }, - - beforeDestroy() { - this.connection.dispose(); - }, - - methods: { - onStreamDriveFileCreated(file) { - this.$refs.drive.prepend(file); - }, - - onStreamDriveFileUpdated(file) { - // TODO - }, - - onStreamDriveFileDeleted(fileId) { - this.$refs.drive.remove(x => x.id === fileId); - }, - - download() { - window.open(this.selected.url, '_blank'); - }, - - async del() { - const { canceled } = await this.$root.dialog({ - type: 'warning', - text: this.$t('driveFileDeleteConfirm', { name: this.selected.name }), - showCancelButton: true - }); - if (canceled) return; - - this.$root.api('drive/files/delete', { - fileId: this.selected.id - }); - } - } -}); -</script> - -<style lang="scss" scoped> -.uawsfosz { - > ._content { - max-height: 350px; - overflow: auto; - - > .drive { - > .file { - display: grid; - margin: 0 auto; - grid-template-columns: 64px 1fr; - grid-column-gap: 10px; - cursor: pointer; - - &.selected { - background: var(--accent); - box-shadow: 0 0 0 8px var(--accent); - color: #fff; - } - - &:not(:last-child) { - margin-bottom: 16px; - } - - > .thumbnail { - width: 64px; - height: 64px; - } - - > .body { - display: block; - word-break: break-all; - padding-top: 4px; - - > .name { - display: block; - margin: 0; - padding: 0; - font-size: 0.9em; - font-weight: bold; - word-break: break-word; - - > .ext { - opacity: 0.5; - } - } - - > .tags { - display: block; - margin: 4px 0 0 0; - padding: 0; - list-style: none; - font-size: 0.5em; - - > .tag { - display: inline-block; - margin: 0 5px 0 0; - padding: 1px 5px; - border-radius: 2px; - } - } - - > footer { - display: block; - margin: 4px 0 0 0; - font-size: 0.7em; - - > .separator { - padding: 0 4px; - } - - > .type { - opacity: 0.7; - - > .icon { - margin-right: 4px; - } - } - - > .data-size { - opacity: 0.7; - } - - > .created-at { - opacity: 0.7; - - > [data-icon] { - margin-right: 2px; - } - } - - > .nsfw { - color: #bf4633; - } - } - } - } - } - } -} -</style> 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/import-export.vue b/src/client/pages/settings/import-export.vue deleted file mode 100644 index 4795741189..0000000000 --- a/src/client/pages/settings/import-export.vue +++ /dev/null @@ -1,121 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faBoxes"/> {{ $t('importAndExport') }}</div> - <div class="_content"> - <mk-select v-model="exportTarget"> - <option value="notes">{{ $t('_exportOrImport.allNotes') }}</option> - <option value="following">{{ $t('_exportOrImport.followingList') }}</option> - <option value="user-lists">{{ $t('_exportOrImport.userLists') }}</option> - <option value="mute">{{ $t('_exportOrImport.muteList') }}</option> - <option value="blocking">{{ $t('_exportOrImport.blockingList') }}</option> - </mk-select> - <mk-button inline @click="doExport()"><fa :icon="faDownload"/> {{ $t('export') }}</mk-button> - <mk-button inline @click="doImport()" :disabled="!['following', 'user-lists'].includes(exportTarget)"><fa :icon="faUpload"/> {{ $t('import') }}</mk-button> - </div> - <input ref="file" type="file" style="display: none;" @change="onChangeFile"/> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faDownload, faUpload, faBoxes } from '@fortawesome/free-solid-svg-icons'; -import MkButton from '../../components/ui/button.vue'; -import MkSelect from '../../components/ui/select.vue'; -import i18n from '../../i18n'; -import { apiUrl } from '../../config'; - -export default Vue.extend({ - i18n, - - components: { - MkButton, - MkSelect, - }, - - data() { - return { - exportTarget: 'notes', - faDownload, faUpload, faBoxes - } - }, - - methods: { - doExport() { - this.$root.api( - this.exportTarget == 'notes' ? 'i/export-notes' : - this.exportTarget == 'following' ? 'i/export-following' : - this.exportTarget == 'blocking' ? 'i/export-blocking' : - this.exportTarget == 'user-lists' ? 'i/export-user-lists' : - null, {}) - .then(() => { - this.$root.dialog({ - type: 'info', - text: this.$t('exportRequested') - }); - }).catch((e: any) => { - this.$root.dialog({ - type: 'error', - text: e.message - }); - }); - }, - - doImport() { - (this.$refs.file as any).click(); - }, - - onChangeFile() { - const [file] = Array.from((this.$refs.file as any).files); - - const data = new FormData(); - data.append('file', file); - data.append('i', this.$store.state.i.token); - - const dialog = this.$root.dialog({ - type: 'waiting', - text: this.$t('uploading') + '...', - showOkButton: false, - showCancelButton: false, - cancelableByBgClick: false - }); - - fetch(apiUrl + '/drive/files/create', { - method: 'POST', - body: data - }) - .then(response => response.json()) - .then(f => { - this.reqImport(f); - }) - .catch(e => { - this.$root.dialog({ - type: 'error', - text: e - }); - }) - .finally(() => { - dialog.close(); - }); - }, - - reqImport(file) { - this.$root.api( - this.exportTarget == 'following' ? 'i/import-following' : - this.exportTarget == 'user-lists' ? 'i/import-user-lists' : - null, { - fileId: file.id - }).then(() => { - this.$root.dialog({ - type: 'info', - text: this.$t('importRequested') - }); - }).catch((e: any) => { - this.$root.dialog({ - type: 'error', - text: e.message - }); - }); - } - } -}); -</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/integration.vue b/src/client/pages/settings/integration.vue deleted file mode 100644 index 742d432018..0000000000 --- a/src/client/pages/settings/integration.vue +++ /dev/null @@ -1,131 +0,0 @@ -<template> -<section class="_card" v-if="enableTwitterIntegration || enableDiscordIntegration || enableGithubIntegration"> - <div class="_title"><fa :icon="faShareAlt"/> {{ $t('integration') }}</div> - - <div class="_content" v-if="enableTwitterIntegration"> - <header><fa :icon="faTwitter"/> Twitter</header> - <p v-if="integrations.twitter">{{ $t('connectedTo') }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <mk-button v-if="integrations.twitter" @click="disconnectTwitter">{{ $t('disconnectSerice') }}</mk-button> - <mk-button v-else @click="connectTwitter">{{ $t('connectSerice') }}</mk-button> - </div> - - <div class="_content" v-if="enableDiscordIntegration"> - <header><fa :icon="faDiscord"/> Discord</header> - <p v-if="integrations.discord">{{ $t('connectedTo') }}: <a :href="`https://discordapp.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <mk-button v-if="integrations.discord" @click="disconnectDiscord">{{ $t('disconnectSerice') }}</mk-button> - <mk-button v-else @click="connectDiscord">{{ $t('connectSerice') }}</mk-button> - </div> - - <div class="_content" v-if="enableGithubIntegration"> - <header><fa :icon="faGithub"/> GitHub</header> - <p v-if="integrations.github">{{ $t('connectedTo') }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <mk-button v-if="integrations.github" @click="disconnectGithub">{{ $t('disconnectSerice') }}</mk-button> - <mk-button v-else @click="connectGithub">{{ $t('connectSerice') }}</mk-button> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faShareAlt } from '@fortawesome/free-solid-svg-icons'; -import { faTwitter, faDiscord, faGithub } from '@fortawesome/free-brands-svg-icons'; -import i18n from '../../i18n'; -import { apiUrl } from '../../config'; -import MkButton from '../../components/ui/button.vue'; - -export default Vue.extend({ - i18n, - - components: { - MkButton - }, - - data() { - return { - apiUrl, - twitterForm: null, - discordForm: null, - githubForm: null, - enableTwitterIntegration: false, - enableDiscordIntegration: false, - enableGithubIntegration: false, - faShareAlt, faTwitter, faDiscord, faGithub - }; - }, - - computed: { - integrations() { - return this.$store.state.i.integrations; - }, - - meta() { - return this.$store.state.instance.meta; - }, - }, - - created() { - this.enableTwitterIntegration = this.meta.enableTwitterIntegration; - this.enableDiscordIntegration = this.meta.enableDiscordIntegration; - this.enableGithubIntegration = this.meta.enableGithubIntegration; - }, - - mounted() { - if (!document.cookie.match(/i=(\w+)/)) { - document.cookie = `i=${this.$store.state.i.token}; path=/;` + - ` domain=${document.location.hostname}; max-age=31536000;` + - (document.location.protocol.startsWith('https') ? ' secure' : ''); - } - this.$watch('integrations', () => { - if (this.integrations.twitter) { - if (this.twitterForm) this.twitterForm.close(); - } - if (this.integrations.discord) { - if (this.discordForm) this.discordForm.close(); - } - if (this.integrations.github) { - if (this.githubForm) this.githubForm.close(); - } - }, { - deep: true - }); - }, - - methods: { - connectTwitter() { - this.twitterForm = window.open(apiUrl + '/connect/twitter', - 'twitter_connect_window', - 'height=570, width=520'); - }, - - disconnectTwitter() { - window.open(apiUrl + '/disconnect/twitter', - 'twitter_disconnect_window', - 'height=570, width=520'); - }, - - connectDiscord() { - this.discordForm = window.open(apiUrl + '/connect/discord', - 'discord_connect_window', - 'height=570, width=520'); - }, - - disconnectDiscord() { - window.open(apiUrl + '/disconnect/discord', - 'discord_disconnect_window', - 'height=570, width=520'); - }, - - connectGithub() { - this.githubForm = window.open(apiUrl + '/connect/github', - 'github_connect_window', - 'height=570, width=520'); - }, - - disconnectGithub() { - window.open(apiUrl + '/disconnect/github', - 'github_disconnect_window', - 'height=570, width=520'); - }, - } -}); -</script> diff --git a/src/client/pages/settings/mute-block.vue b/src/client/pages/settings/mute-block.vue deleted file mode 100644 index 03cf4aacc8..0000000000 --- a/src/client/pages/settings/mute-block.vue +++ /dev/null @@ -1,76 +0,0 @@ -<template> -<section class="rrfwjxfl _card"> - <div class="_title"><fa :icon="faBan"/> {{ $t('muteAndBlock') }}</div> - <div class="_content"> - <span>{{ $t('mutedUsers') }}</span> - <mk-pagination :pagination="mutingPagination" class="muting"> - <template #empty><span>{{ $t('noUsers') }}</span></template> - <template #default="{items}"> - <div class="user" v-for="(mute, i) in items" :key="mute.id"> - <router-link class="name" :to="mute.mutee | userPage"> - <mk-acct :user="mute.mutee"/> - </router-link> - </div> - </template> - </mk-pagination> - </div> - <div class="_content"> - <span>{{ $t('blockedUsers') }}</span> - <mk-pagination :pagination="blockingPagination" class="blocking"> - <template #empty><span>{{ $t('noUsers') }}</span></template> - <template #default="{items}"> - <div class="user" v-for="(block, i) in items" :key="block.id"> - <router-link class="name" :to="block.blockee | userPage"> - <mk-acct :user="block.blockee"/> - </router-link> - </div> - </template> - </mk-pagination> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faBan } from '@fortawesome/free-solid-svg-icons'; -import MkPagination from '../../components/ui/pagination.vue'; -import i18n from '../../i18n'; - -export default Vue.extend({ - i18n, - - components: { - MkPagination, - }, - - data() { - return { - mutingPagination: { - endpoint: 'mute/list', - limit: 10, - }, - blockingPagination: { - endpoint: 'blocking/list', - limit: 10, - }, - faBan - } - }, -}); -</script> - -<style lang="scss" scoped> -.rrfwjxfl { - > ._content { - max-height: 350px; - overflow: auto; - - > .muting, - > .blocking { - > .empty { - opacity: 0.5 !important; - } - } - } -} -</style> diff --git a/src/client/pages/settings/privacy.vue b/src/client/pages/settings/privacy.vue deleted file mode 100644 index 7ac9062d88..0000000000 --- a/src/client/pages/settings/privacy.vue +++ /dev/null @@ -1,76 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faLock"/> {{ $t('privacy') }}</div> - <div class="_content"> - <mk-switch v-model="isLocked" @change="save()">{{ $t('makeFollowManuallyApprove') }}</mk-switch> - <mk-switch v-model="autoAcceptFollowed" v-if="isLocked" @change="save()">{{ $t('autoAcceptFollowed') }}</mk-switch> - </div> - <div class="_content"> - <mk-switch v-model="rememberNoteVisibility" @change="save()">{{ $t('rememberNoteVisibility') }}</mk-switch> - <mk-select v-model="defaultNoteVisibility" style="margin-bottom: 8px;" v-if="!rememberNoteVisibility"> - <template #label>{{ $t('defaultNoteVisibility') }}</template> - <option value="public">{{ $t('_visibility.public') }}</option> - <option value="home">{{ $t('_visibility.home') }}</option> - <option value="followers">{{ $t('_visibility.followers') }}</option> - <option value="specified">{{ $t('_visibility.specified') }}</option> - </mk-select> - <mk-switch v-model="defaultNoteLocalOnly" v-if="!rememberNoteVisibility">{{ $t('_visibility.localOnly') }}</mk-switch> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faLock } from '@fortawesome/free-solid-svg-icons'; -import MkSelect from '../../components/ui/select.vue'; -import MkSwitch from '../../components/ui/switch.vue'; -import i18n from '../../i18n'; - -export default Vue.extend({ - i18n, - - components: { - MkSelect, - MkSwitch, - }, - - data() { - return { - isLocked: false, - autoAcceptFollowed: false, - faLock - } - }, - - computed: { - defaultNoteVisibility: { - get() { return this.$store.state.settings.defaultNoteVisibility; }, - set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteVisibility', value }); } - }, - - defaultNoteLocalOnly: { - get() { return this.$store.state.settings.defaultNoteLocalOnly; }, - set(value) { this.$store.dispatch('settings/set', { key: 'defaultNoteLocalOnly', value }); } - }, - - rememberNoteVisibility: { - get() { return this.$store.state.settings.rememberNoteVisibility; }, - set(value) { this.$store.dispatch('settings/set', { key: 'rememberNoteVisibility', value }); } - }, - }, - - created() { - this.isLocked = this.$store.state.i.isLocked; - this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed; - }, - - methods: { - save() { - this.$root.api('i/update', { - isLocked: !!this.isLocked, - autoAcceptFollowed: !!this.autoAcceptFollowed, - }); - } - } -}); -</script> diff --git a/src/client/pages/settings/profile.vue b/src/client/pages/settings/profile.vue deleted file mode 100644 index b168c89ec0..0000000000 --- a/src/client/pages/settings/profile.vue +++ /dev/null @@ -1,223 +0,0 @@ -<template> -<section class="llvierxe _card"> - <div class="_title"><fa :icon="faUser"/> {{ $t('profile') }}<small style="display: block; font-weight: normal; opacity: 0.6;">@{{ $store.state.i.username }}@{{ host }}</small></div> - <div class="_content"> - <div class="header" :style="{ backgroundImage: $store.state.i.bannerUrl ? `url(${ $store.state.i.bannerUrl })` : null }" @click="changeBanner"> - <mk-avatar class="avatar" :user="$store.state.i" :disable-preview="true" :disable-link="true" @click.stop="changeAvatar"/> - </div> - - <mk-input v-model="name" :max="30"> - <span>{{ $t('_profile.name') }}</span> - </mk-input> - - <mk-textarea v-model="description" :max="500"> - <span>{{ $t('_profile.description') }}</span> - <template #desc>{{ $t('_profile.youCanIncludeHashtags') }}</template> - </mk-textarea> - - <mk-input v-model="location"> - <span>{{ $t('location') }}</span> - <template #prefix><fa :icon="faMapMarkerAlt"/></template> - </mk-input> - - <mk-input v-model="birthday" type="date"> - <template #title>{{ $t('birthday') }}</template> - <template #prefix><fa :icon="faBirthdayCake"/></template> - </mk-input> - - <details class="fields"> - <summary>{{ $t('_profile.metadata') }}</summary> - <div class="row"> - <mk-input v-model="fieldName0">{{ $t('_profile.metadataLabel') }}</mk-input> - <mk-input v-model="fieldValue0">{{ $t('_profile.metadataContent') }}</mk-input> - </div> - <div class="row"> - <mk-input v-model="fieldName1">{{ $t('_profile.metadataLabel') }}</mk-input> - <mk-input v-model="fieldValue1">{{ $t('_profile.metadataContent') }}</mk-input> - </div> - <div class="row"> - <mk-input v-model="fieldName2">{{ $t('_profile.metadataLabel') }}</mk-input> - <mk-input v-model="fieldValue2">{{ $t('_profile.metadataContent') }}</mk-input> - </div> - <div class="row"> - <mk-input v-model="fieldName3">{{ $t('_profile.metadataLabel') }}</mk-input> - <mk-input v-model="fieldValue3">{{ $t('_profile.metadataContent') }}</mk-input> - </div> - </details> - - <mk-switch v-model="isBot">{{ $t('flagAsBot') }}</mk-switch> - <mk-switch v-model="isCat">{{ $t('flagAsCat') }}</mk-switch> - </div> - <div class="_footer"> - <mk-button @click="save(true)" primary><fa :icon="faSave"/> {{ $t('save') }}</mk-button> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faUnlockAlt, faCogs, faUser, faMapMarkerAlt, faBirthdayCake } from '@fortawesome/free-solid-svg-icons'; -import { faSave } from '@fortawesome/free-regular-svg-icons'; -import MkButton from '../../components/ui/button.vue'; -import MkInput from '../../components/ui/input.vue'; -import MkTextarea from '../../components/ui/textarea.vue'; -import MkSwitch from '../../components/ui/switch.vue'; -import i18n from '../../i18n'; -import { host } from '../../config'; -import { selectFile } from '../../scripts/select-file'; - -export default Vue.extend({ - i18n, - - components: { - MkButton, - MkInput, - MkTextarea, - MkSwitch, - }, - - data() { - return { - host, - name: null, - description: null, - birthday: null, - location: null, - fieldName0: null, - fieldValue0: null, - fieldName1: null, - fieldValue1: null, - fieldName2: null, - fieldValue2: null, - fieldName3: null, - fieldValue3: null, - avatarId: null, - bannerId: null, - isBot: false, - isCat: false, - saving: false, - faSave, faUnlockAlt, faCogs, faUser, faMapMarkerAlt, faBirthdayCake - } - }, - - created() { - this.name = this.$store.state.i.name; - this.description = this.$store.state.i.description; - this.location = this.$store.state.i.location; - this.birthday = this.$store.state.i.birthday; - this.avatarId = this.$store.state.i.avatarId; - this.bannerId = this.$store.state.i.bannerId; - this.isBot = this.$store.state.i.isBot; - this.isCat = this.$store.state.i.isCat; - - this.fieldName0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].name : null; - this.fieldValue0 = this.$store.state.i.fields[0] ? this.$store.state.i.fields[0].value : null; - this.fieldName1 = this.$store.state.i.fields[1] ? this.$store.state.i.fields[1].name : null; - this.fieldValue1 = this.$store.state.i.fields[1] ? this.$store.state.i.fields[1].value : null; - this.fieldName2 = this.$store.state.i.fields[2] ? this.$store.state.i.fields[2].name : null; - this.fieldValue2 = this.$store.state.i.fields[2] ? this.$store.state.i.fields[2].value : null; - this.fieldName3 = this.$store.state.i.fields[3] ? this.$store.state.i.fields[3].name : null; - this.fieldValue3 = this.$store.state.i.fields[3] ? this.$store.state.i.fields[3].value : null; - }, - - methods: { - changeAvatar(e) { - selectFile(this, e.currentTarget || e.target, this.$t('avatar')).then(file => { - this.$root.api('i/update', { - avatarId: file.id, - }); - }); - }, - - changeBanner(e) { - selectFile(this, e.currentTarget || e.target, this.$t('banner')).then(file => { - this.$root.api('i/update', { - bannerId: file.id, - }); - }); - }, - - save(notify) { - const fields = [ - { name: this.fieldName0, value: this.fieldValue0 }, - { name: this.fieldName1, value: this.fieldValue1 }, - { name: this.fieldName2, value: this.fieldValue2 }, - { name: this.fieldName3, value: this.fieldValue3 }, - ]; - - this.saving = true; - - this.$root.api('i/update', { - name: this.name || null, - description: this.description || null, - location: this.location || null, - birthday: this.birthday || null, - fields, - isBot: !!this.isBot, - isCat: !!this.isCat, - }).then(i => { - this.saving = false; - this.$store.state.i.avatarId = i.avatarId; - this.$store.state.i.avatarUrl = i.avatarUrl; - this.$store.state.i.bannerId = i.bannerId; - this.$store.state.i.bannerUrl = i.bannerUrl; - - if (notify) { - this.$root.dialog({ - type: 'success', - iconOnly: true, autoClose: true - }); - } - }).catch(err => { - this.saving = false; - this.$root.dialog({ - type: 'error', - text: err.id - }); - }); - }, - } -}); -</script> - -<style lang="scss" scoped> -.llvierxe { - > ._content { - > .header { - position: relative; - height: 150px; - overflow: hidden; - background-size: cover; - background-position: center; - border-radius: 5px; - border: solid 1px var(--divider); - box-sizing: border-box; - cursor: pointer; - - > .avatar { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: block; - width: 72px; - height: 72px; - margin: auto; - cursor: pointer; - box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5); - } - } - - > .fields { - > .row { - > * { - display: inline-block; - width: 50%; - margin-bottom: 0; - } - } - } - } -} -</style> diff --git a/src/client/pages/settings/reaction.vue b/src/client/pages/settings/reaction.vue deleted file mode 100644 index 250769ec9e..0000000000 --- a/src/client/pages/settings/reaction.vue +++ /dev/null @@ -1,62 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faLaugh"/> {{ $t('reaction') }}</div> - <div class="_content"> - <mk-textarea v-model="reactions">{{ $t('reaction') }}<template #desc>{{ $t('reactionSettingDescription') }}</template></mk-textarea> - </div> - <div class="_footer"> - <mk-button @click="save()" primary inline :disabled="!changed"><fa :icon="faSave"/> {{ $t('save') }}</mk-button> - <mk-button inline @click="preview"><fa :icon="faEye"/> {{ $t('preview') }}</mk-button> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons'; -import MkTextarea from '../../components/ui/textarea.vue'; -import MkButton from '../../components/ui/button.vue'; -import MkReactionPicker from '../../components/reaction-picker.vue'; -import i18n from '../../i18n'; - -export default Vue.extend({ - i18n, - - components: { - MkTextarea, - MkButton, - }, - - data() { - return { - reactions: this.$store.state.settings.reactions.join('\n'), - changed: false, - faLaugh, faSave, faEye - } - }, - - watch: { - reactions() { - this.changed = true; - } - }, - - methods: { - save() { - this.$store.dispatch('settings/set', { key: 'reactions', value: this.reactions.trim().split('\n') }); - this.changed = false; - }, - - preview(ev) { - const picker = this.$root.new(MkReactionPicker, { - source: ev.currentTarget || ev.target, - reactions: this.reactions.trim().split('\n'), - showFocus: false, - }); - picker.$once('chosen', reaction => { - picker.close(); - }); - } - } -}); -</script> diff --git a/src/client/pages/settings/security.vue b/src/client/pages/settings/security.vue deleted file mode 100644 index ba670b2f68..0000000000 --- a/src/client/pages/settings/security.vue +++ /dev/null @@ -1,87 +0,0 @@ -<template> -<section class="_card"> - <div class="_title"><fa :icon="faLock"/> {{ $t('password') }}</div> - <div class="_content"> - <mk-button primary @click="change()">{{ $t('changePassword') }}</mk-button> - </div> -</section> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import { faLock } from '@fortawesome/free-solid-svg-icons'; -import MkButton from '../../components/ui/button.vue'; -import i18n from '../../i18n'; - -export default Vue.extend({ - i18n, - - components: { - MkButton, - }, - - data() { - return { - faLock - } - }, - - methods: { - async change() { - const { canceled: canceled1, result: currentPassword } = await this.$root.dialog({ - title: this.$t('currentPassword'), - input: { - type: 'password' - } - }); - if (canceled1) return; - - const { canceled: canceled2, result: newPassword } = await this.$root.dialog({ - title: this.$t('newPassword'), - input: { - type: 'password' - } - }); - if (canceled2) return; - - const { canceled: canceled3, result: newPassword2 } = await this.$root.dialog({ - title: this.$t('newPasswordRetype'), - input: { - type: 'password' - } - }); - if (canceled3) return; - - if (newPassword !== newPassword2) { - this.$root.dialog({ - type: 'error', - text: this.$t('retypedNotMatch') - }); - return; - } - - const dialog = this.$root.dialog({ - type: 'waiting', - iconOnly: true - }); - - this.$root.api('i/change-password', { - currentPassword, - newPassword - }).then(() => { - this.$root.dialog({ - type: 'success', - iconOnly: true, autoClose: true - }); - }).catch(e => { - this.$root.dialog({ - type: 'error', - text: e - }); - }).finally(() => { - dialog.close(); - }); - } - } -}); -</script> 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> |