summaryrefslogtreecommitdiff
path: root/src/client/pages/settings
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2020-02-17 03:10:51 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2020-02-17 03:10:51 +0900
commit93d25a2a34df72fd8ce62cce33fa098cf11d5eb1 (patch)
treec1a5ba7407fc0ea3053caa660af2dd514f8599b7 /src/client/pages/settings
parent:art: (diff)
downloadmisskey-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.vue253
-rw-r--r--src/client/pages/settings/api.vue46
-rw-r--r--src/client/pages/settings/drive.vue212
-rw-r--r--src/client/pages/settings/general.vue159
-rw-r--r--src/client/pages/settings/import-export.vue121
-rw-r--r--src/client/pages/settings/index.vue144
-rw-r--r--src/client/pages/settings/integration.vue131
-rw-r--r--src/client/pages/settings/mute-block.vue76
-rw-r--r--src/client/pages/settings/privacy.vue76
-rw-r--r--src/client/pages/settings/profile.vue223
-rw-r--r--src/client/pages/settings/reaction.vue62
-rw-r--r--src/client/pages/settings/security.vue87
-rw-r--r--src/client/pages/settings/theme.vue22
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>