nanka iroiro (#6853)

* wip

* Update maps.ts

* wip

* wip

* wip

* wip

* Update base.vue

* wip

* wip

* wip

* wip

* Update link.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update privacy.vue

* wip

* wip

* wip

* wip

* Update range.vue

* wip

* wip

* wip

* wip

* Update profile.vue

* wip

* Update a.vue

* Update index.vue

* wip

* Update sidebar.vue

* wip

* wip

* Update account-info.vue

* Update a.vue

* wip

* wip

* Update sounds.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update account-info.vue

* Update account-info.vue

* wip

* wip

* wip

* Update d-persimmon.json5

* wip
This commit is contained in:
syuilo 2020-11-25 21:31:34 +09:00 committed by GitHub
parent 7660839e40
commit 0144408500
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
106 changed files with 4489 additions and 1734 deletions

View file

@ -1,6 +1,6 @@
<template>
<div class="_section">
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content" ref="list">
<MkPagination :pagination="pagination" #default="{items}" class="ruryvtyk _content">
<section class="_card announcement _vMargin" v-for="(announcement, i) in items" :key="announcement.id">
<div class="_title"><span v-if="$store.getters.isSignedIn && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
<div class="_content">

View file

@ -7,6 +7,8 @@
<MkTextarea v-model:value="description">{{ $t('instanceDescription') }}</MkTextarea>
<MkInput v-model:value="iconUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('iconUrl') }}</MkInput>
<MkInput v-model:value="bannerUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('bannerUrl') }}</MkInput>
<MkInput v-model:value="backgroundImageUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('backgroundImageUrl') }}</MkInput>
<MkInput v-model:value="logoImageUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('logoImageUrl') }}</MkInput>
<MkInput v-model:value="tosUrl"><template #icon><Fa :icon="faLink"/></template>{{ $t('tosUrl') }}</MkInput>
<MkInput v-model:value="maintainerName">{{ $t('maintainerName') }}</MkInput>
<MkInput v-model:value="maintainerEmail" type="email"><template #icon><Fa :icon="faEnvelope"/></template>{{ $t('maintainerEmail') }}</MkInput>
@ -292,6 +294,8 @@ export default defineComponent({
email: null,
bannerUrl: null,
iconUrl: null,
logoImageUrl: null,
backgroundImageUrl: null,
maxNoteTextLength: 0,
enableRegistration: false,
enableLocalTimeline: false,
@ -345,6 +349,8 @@ export default defineComponent({
this.tosUrl = this.meta.tosUrl;
this.bannerUrl = this.meta.bannerUrl;
this.iconUrl = this.meta.iconUrl;
this.logoImageUrl = this.meta.logoImageUrl;
this.backgroundImageUrl = this.meta.backgroundImageUrl;
this.enableEmail = this.meta.enableEmail;
this.email = this.meta.email;
this.maintainerName = this.meta.maintainerName;
@ -498,6 +504,8 @@ export default defineComponent({
tosUrl: this.tosUrl,
bannerUrl: this.bannerUrl,
iconUrl: this.iconUrl,
logoImageUrl: this.logoImageUrl,
backgroundImageUrl: this.backgroundImageUrl,
maintainerName: this.maintainerName,
maintainerEmail: this.maintainerEmail,
maxNoteTextLength: this.maxNoteTextLength,

View file

@ -38,6 +38,7 @@ import parseAcct from '../../../misc/acct/parse';
import { isBottom, onScrollBottom, scroll } from '@/scripts/scroll';
import * as os from '@/os';
import { popout } from '@/scripts/popout';
import * as sound from '@/scripts/sound';
const Component = defineComponent({
components: {
@ -218,7 +219,7 @@ const Component = defineComponent({
},
onMessage(message) {
os.sound('chat');
sound.play('chat');
const _isBottom = isBottom(this.$el, 64);

View file

@ -94,6 +94,7 @@ import { url } from '@/config';
import MkButton from '@/components/ui/button.vue';
import { userPage } from '@/filters/user';
import * as os from '@/os';
import * as sound from '@/scripts/sound';
export default defineComponent({
components: {
@ -245,11 +246,7 @@ export default defineComponent({
this.o.put(this.myColor, pos);
//
if (this.$store.state.device.enableSounds) {
const sound = new Audio(`${url}/assets/reversi-put-me.mp3`);
sound.volume = this.$store.state.device.soundVolume;
sound.play();
}
sound.play(this.myColor ? 'reversiPutBlack' : 'reversiPutWhite');
this.connection.send('set', {
pos: pos
@ -268,10 +265,8 @@ export default defineComponent({
this.$forceUpdate();
//
if (this.$store.state.device.enableSounds && x.color != this.myColor) {
const sound = new Audio(`${url}/assets/reversi-put-you.mp3`);
sound.volume = this.$store.state.device.soundVolume;
sound.play();
if (x.color !== this.myColor) {
sound.play(x.color ? 'reversiPutBlack' : 'reversiPutWhite');
}
},

View file

@ -75,14 +75,25 @@ 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';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import * as os from '@/os';
export default defineComponent({
components: {
FormBase,
MkButton, MkInfo, MkInput, MkSwitch
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('twoStepAuthentication'),
icon: faLock
},
data: null,
supportsCredentials: !!navigator.credentials,
usePasswordLessLogin: this.$store.state.i.usePasswordLessLogin,
@ -92,6 +103,7 @@ export default defineComponent({
faLock
};
},
methods: {
register() {
os.dialog({
@ -225,6 +237,7 @@ export default defineComponent({
});
});
},
updatePasswordLessLogin() {
os.api('i/2fa/password-less', {
value: !!this.usePasswordLessLogin

View file

@ -0,0 +1,185 @@
<template>
<FormBase>
<FormKeyValueView>
<template #key>ID</template>
<template #value><span class="_monospace">{{ $store.state.i.id }}</span></template>
</FormKeyValueView>
<FormGroup>
<FormKeyValueView>
<template #key>{{ $t('registeredDate') }}</template>
<template #value><MkTime :time="$store.state.i.createdAt" mode="detail"/></template>
</FormKeyValueView>
</FormGroup>
<FormGroup v-if="stats">
<template #label>{{ $t('statistics') }}</template>
<FormKeyValueView>
<template #key>{{ $t('notesCount') }}</template>
<template #value>{{ number(stats.notesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('repliesCount') }}</template>
<template #value>{{ number(stats.repliesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('renotesCount') }}</template>
<template #value>{{ number(stats.renotesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('repliedCount') }}</template>
<template #value>{{ number(stats.repliedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('renotedCount') }}</template>
<template #value>{{ number(stats.renotedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('pollVotesCount') }}</template>
<template #value>{{ number(stats.pollVotesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('pollVotedCount') }}</template>
<template #value>{{ number(stats.pollVotedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('sentReactionsCount') }}</template>
<template #value>{{ number(stats.sentReactionsCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('receivedReactionsCount') }}</template>
<template #value>{{ number(stats.receivedReactionsCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('noteFavoritesCount') }}</template>
<template #value>{{ number(stats.noteFavoritesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('followingCount') }}</template>
<template #value>{{ number(stats.followingCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('followingCount') }} ({{ $t('local') }})</template>
<template #value>{{ number(stats.localFollowingCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('followingCount') }} ({{ $t('remote') }})</template>
<template #value>{{ number(stats.remoteFollowingCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('followersCount') }}</template>
<template #value>{{ number(stats.followersCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('followersCount') }} ({{ $t('local') }})</template>
<template #value>{{ number(stats.localFollowersCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('followersCount') }} ({{ $t('remote') }})</template>
<template #value>{{ number(stats.remoteFollowersCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('pageLikesCount') }}</template>
<template #value>{{ number(stats.pageLikesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('pageLikedCount') }}</template>
<template #value>{{ number(stats.pageLikedCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('driveFilesCount') }}</template>
<template #value>{{ number(stats.driveFilesCount) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('driveUsage') }}</template>
<template #value>{{ bytes(stats.driveUsage) }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>{{ $t('reversiCount') }}</template>
<template #value>{{ number(stats.reversiCount) }}</template>
</FormKeyValueView>
</FormGroup>
<FormGroup>
<template #label>{{ $t('other') }}</template>
<FormKeyValueView>
<template #key>emailVerified</template>
<template #value>{{ $store.state.i.emailVerified ? $t('yes') : $t('no') }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>twoFactorEnabled</template>
<template #value>{{ $store.state.i.twoFactorEnabled ? $t('yes') : $t('no') }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>securityKeys</template>
<template #value>{{ $store.state.i.securityKeys ? $t('yes') : $t('no') }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>usePasswordLessLogin</template>
<template #value>{{ $store.state.i.usePasswordLessLogin ? $t('yes') : $t('no') }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>isModerator</template>
<template #value>{{ $store.state.i.isModerator ? $t('yes') : $t('no') }}</template>
</FormKeyValueView>
<FormKeyValueView>
<template #key>isAdmin</template>
<template #value>{{ $store.state.i.isAdmin ? $t('yes') : $t('no') }}</template>
</FormKeyValueView>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
export default defineComponent({
components: {
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
FormKeyValueView,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('accountInfo'),
icon: faInfoCircle
},
stats: null
}
},
mounted() {
this.$emit('info', this.INFO);
os.api('users/stats', {
userId: this.$store.state.i.id
}).then(stats => {
this.stats = stats;
});
},
methods: {
number,
bytes,
}
});
</script>

View file

@ -1,26 +1,27 @@
<template>
<div>
<div class="_section">
<div class="_content">
<MkButton @click="generateToken">{{ $t('generateAccessToken') }}</MkButton>
</div>
</div>
<div class="_section">
<MkA to="/api-console" :behavior="isDesktop ? 'window' : null">API console</MkA>
</div>
</div>
<FormBase>
<FormButton @click="generateToken" primary>{{ $t('generateAccessToken') }}</FormButton>
<FormLink to="/settings/apps">{{ $t('manageAccessTokens') }}</FormLink>
<FormLink to="/api-console" :behavior="isDesktop ? 'window' : null">API console</FormLink>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faKey } from '@fortawesome/free-solid-svg-icons';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/ui/input.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton, MkInput
FormBase,
FormButton,
FormLink,
},
emits: ['info'],

View file

@ -1,6 +1,6 @@
<template>
<div>
<MkPagination :pagination="pagination" class="bfomjevm" ref="list">
<FormBase>
<FormPagination :pagination="pagination" ref="list">
<template #empty>
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@ -8,8 +8,8 @@
</div>
</template>
<template #default="{items}">
<div class="token _panel" v-for="token in items" :key="token.id">
<img class="icon" :src="token.iconUrl" alt=""/>
<div class="_formPanel bfomjevm" v-for="token in items" :key="token.id">
<img class="icon" :src="token.iconUrl" alt="" v-if="token.iconUrl"/>
<div class="body">
<div class="name">{{ token.name }}</div>
<div class="description">{{ token.description }}</div>
@ -33,21 +33,29 @@
</div>
</div>
</template>
</MkPagination>
</div>
</FormPagination>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faTrashAlt, faPlug } from '@fortawesome/free-solid-svg-icons';
import MkPagination from '@/components/ui/pagination.vue';
import FormPagination from '@/components/form/pagination.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkPagination
FormBase,
FormPagination,
},
emits: ['info'],
data() {
return {
INFO: {
@ -65,6 +73,10 @@ export default defineComponent({
};
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
revoke(token) {
os.api('i/revoke-token', { tokenId: token.id }).then(() => {
@ -77,26 +89,24 @@ export default defineComponent({
<style lang="scss" scoped>
.bfomjevm {
> .token {
display: flex;
padding: 16px;
display: flex;
padding: 16px;
> .icon {
display: block;
flex-shrink: 0;
margin: 0 12px 0 0;
width: 50px;
height: 50px;
border-radius: 8px;
}
> .icon {
display: block;
flex-shrink: 0;
margin: 0 12px 0 0;
width: 50px;
height: 50px;
border-radius: 8px;
}
> .body {
width: calc(100% - 62px);
position: relative;
> .body {
width: calc(100% - 62px);
position: relative;
> .name {
font-weight: bold;
}
> .name {
font-weight: bold;
}
}
}

View file

@ -0,0 +1,90 @@
<template>
<FormBase>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faColumns"/> </div>
<div class="_content">
<div>{{ $t('defaultNavigationBehaviour') }}</div>
<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch>
</div>
<div class="_content">
<MkSwitch v-model:value="deckAlwaysShowMainColumn">
{{ $t('_deck.alwaysShowMainColumn') }}
</MkSwitch>
</div>
<div class="_content">
<div>{{ $t('_deck.columnAlign') }}</div>
<MkRadio v-model="deckColumnAlign" value="left">{{ $t('left') }}</MkRadio>
<MkRadio v-model="deckColumnAlign" value="center">{{ $t('center') }}</MkRadio>
</div>
</section>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faImage, faCog, faColumns } from '@fortawesome/free-solid-svg-icons';
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 MkRadios from '@/components/ui/radios.vue';
import MkRange from '@/components/ui/range.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import { clientDb, set } from '@/db';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
MkSwitch,
MkSelect,
MkRadio,
MkRadios,
MkRange,
FormSwitch,
FormSelect,
FormRadios,
FormBase,
FormGroup,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('deck'),
icon: faColumns
},
faImage, faCog,
}
},
computed: {
deckNavWindow: {
get() { return this.$store.state.device.deckNavWindow; },
set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); }
},
deckAlwaysShowMainColumn: {
get() { return this.$store.state.device.deckAlwaysShowMainColumn; },
set(value) { this.$store.commit('device/set', { key: 'deckAlwaysShowMainColumn', value }); }
},
deckColumnAlign: {
get() { return this.$store.state.device.deckColumnAlign; },
set(value) { this.$store.commit('device/set', { key: 'deckColumnAlign', value }); }
},
},
mounted() {
this.$emit('info', this.INFO);
},
});
</script>

View file

@ -0,0 +1,71 @@
<template>
<FormBase>
<FormGroup>
<FormInput v-model:value="emailAddress" type="email">
{{ $t('emailAddress') }}
<template #desc v-if="$store.state.i.email && !$store.state.i.emailVerified">{{ $t('verificationEmailSent') }}</template>
<template #desc v-else-if="emailAddress === $store.state.i.email && $store.state.i.emailVerified">{{ $t('emailVerified') }}</template>
</FormInput>
</FormGroup>
<FormButton @click="save" primary>{{ $t('save') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCog } from '@fortawesome/free-solid-svg-icons';
import { faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import FormButton from '@/components/form/button.vue';
import FormInput from '@/components/form/input.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import * as os from '@/os';
export default defineComponent({
components: {
FormBase,
FormInput,
FormButton,
FormGroup,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('emailAddress'),
icon: faEnvelope
},
emailAddress: null,
code: null,
faCog
}
},
created() {
this.emailAddress = this.$store.state.i.email;
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
save() {
os.dialog({
title: this.$t('password'),
input: {
type: 'password'
}
}).then(({ canceled, result: password }) => {
if (canceled) return;
os.api('i/update-email', {
password: password,
email: this.emailAddress,
});
});
}
}
});
</script>

View file

@ -0,0 +1,52 @@
<template>
<FormBase>
<FormGroup>
<template #label>{{ $t('emailAddress') }}</template>
<FormLink to="/settings/email/address">
<template v-if="$store.state.i.email && !$store.state.i.emailVerified" #icon><Fa :icon="faExclamationTriangle" style="color: var(--warn);"/></template>
<template v-else-if="$store.state.i.email && $store.state.i.emailVerified" #icon><Fa :icon="faCheck" style="color: var(--success);"/></template>
{{ $store.state.i.email || $t('notSet') }}
</FormLink>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCog, faExclamationTriangle, faCheck } from '@fortawesome/free-solid-svg-icons';
import { faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import FormButton from '@/components/form/button.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import * as os from '@/os';
export default defineComponent({
components: {
FormBase,
FormLink,
FormButton,
FormGroup,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('email'),
icon: faEnvelope
},
faCog, faExclamationTriangle, faCheck
}
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
}
});
</script>

View file

@ -1,109 +1,110 @@
<template>
<div class="">
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('general') }}</div>
<div class="_content">
<MkRadios v-model="serverDisconnectedBehavior">
<template #desc>{{ $t('whenServerDisconnected') }}</template>
<option value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</option>
<option value="dialog">{{ $t('_serverDisconnectedBehavior.dialog') }}</option>
<option value="quiet">{{ $t('_serverDisconnectedBehavior.quiet') }}</option>
</MkRadios>
<MkSwitch v-model:value="imageNewTab">{{ $t('openImageInNewTab') }}</MkSwitch>
<MkSwitch v-model:value="showFixedPostForm">{{ $t('showFixedPostForm') }}</MkSwitch>
<MkSwitch v-model:value="enableInfiniteScroll">{{ $t('enableInfiniteScroll') }}</MkSwitch>
<MkSwitch v-model:value="disablePagesScript">{{ $t('disablePagesScript') }}</MkSwitch>
<MkSelect v-model:value="lang">
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</MkSelect>
</div>
</section>
<FormBase>
<FormSwitch v-model:value="showFixedPostForm">{{ $t('showFixedPostForm') }}</FormSwitch>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('defaultNavigationBehaviour') }}</div>
<div class="_content">
<MkSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</MkSwitch>
</div>
<div class="_content">
<MkRadios v-model="chatOpenBehavior">
<template #desc>{{ $t('chatOpenBehavior') }}</template>
<option value="page">{{ $t('showInPage') }}</option>
<option value="window">{{ $t('openInWindow') }}</option>
<option value="popout">{{ $t('popout') }}</option>
</MkRadios>
</div>
</section>
<FormSelect v-model:value="lang">
<template #label>{{ $t('uiLanguage') }}</template>
<option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
<template #caption>
<i18n-t keypath="i18nInfo" tag="span">
<template #link>
<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
</template>
</i18n-t>
</template>
</FormSelect>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faCog"/> {{ $t('appearance') }}</div>
<div class="_content">
<MkSwitch v-model:value="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</MkSwitch>
<MkSwitch v-model:value="reduceAnimation">{{ $t('reduceUiAnimation') }}</MkSwitch>
<MkSwitch v-model:value="useBlurEffectForModal">{{ $t('useBlurEffectForModal') }}</MkSwitch>
<MkSwitch v-model:value="useOsNativeEmojis">
{{ $t('useOsNativeEmojis') }}
<template #desc><Mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></template>
</MkSwitch>
<MkRadios v-model="fontSize">
<template #desc>{{ $t('fontSize') }}</template>
<option value="small"><span style="font-size: 14px;">Aa</span></option>
<option :value="null"><span style="font-size: 16px;">Aa</span></option>
<option value="large"><span style="font-size: 18px;">Aa</span></option>
<option value="veryLarge"><span style="font-size: 20px;">Aa</span></option>
</MkRadios>
<MkRadios v-model="instanceTicker">
<template #desc>{{ $t('instanceTicker') }}</template>
<option value="none">{{ $t('_instanceTicker.none') }}</option>
<option value="remote">{{ $t('_instanceTicker.remote') }}</option>
<option value="always">{{ $t('_instanceTicker.always') }}</option>
</MkRadios>
</div>
</section>
<FormGroup>
<template #label>{{ $t('behavior') }}</template>
<FormSwitch v-model:value="imageNewTab">{{ $t('openImageInNewTab') }}</FormSwitch>
<FormSwitch v-model:value="enableInfiniteScroll">{{ $t('enableInfiniteScroll') }}</FormSwitch>
<FormSwitch v-model:value="disablePagesScript">{{ $t('disablePagesScript') }}</FormSwitch>
</FormGroup>
<section class="_card _vMargin">
<div class="_title"><Fa :icon="faColumns"/> {{ $t('deck') }}</div>
<div class="_content">
<div>{{ $t('defaultNavigationBehaviour') }}</div>
<MkSwitch v-model:value="deckNavWindow">{{ $t('openInWindow') }}</MkSwitch>
</div>
<div class="_content">
<MkSwitch v-model:value="deckAlwaysShowMainColumn">
{{ $t('_deck.alwaysShowMainColumn') }}
</MkSwitch>
</div>
<div class="_content">
<div>{{ $t('_deck.columnAlign') }}</div>
<MkRadio v-model="deckColumnAlign" value="left">{{ $t('left') }}</MkRadio>
<MkRadio v-model="deckColumnAlign" value="center">{{ $t('center') }}</MkRadio>
</div>
</section>
<FormSelect v-model:value="serverDisconnectedBehavior">
<template #label>{{ $t('whenServerDisconnected') }}</template>
<option value="reload">{{ $t('_serverDisconnectedBehavior.reload') }}</option>
<option value="dialog">{{ $t('_serverDisconnectedBehavior.dialog') }}</option>
<option value="quiet">{{ $t('_serverDisconnectedBehavior.quiet') }}</option>
</FormSelect>
<MkButton @click="cacheClear()" primary style="margin: var(--margin) auto;">{{ $t('cacheClear') }}</MkButton>
</div>
<FormGroup>
<template #label>{{ $t('appearance') }}</template>
<FormSwitch v-model:value="disableAnimatedMfm">{{ $t('disableAnimatedMfm') }}</FormSwitch>
<FormSwitch v-model:value="reduceAnimation">{{ $t('reduceUiAnimation') }}</FormSwitch>
<FormSwitch v-model:value="useBlurEffectForModal">{{ $t('useBlurEffectForModal') }}</FormSwitch>
<FormSwitch v-model:value="useOsNativeEmojis">{{ $t('useOsNativeEmojis') }}
<div><Mfm text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
</FormSwitch>
<FormSwitch v-model:value="loadRawImages">{{ $t('loadRawImages') }}</FormSwitch>
<FormSwitch v-model:value="disableShowingAnimatedImages">{{ $t('disableShowingAnimatedImages') }}</FormSwitch>
</FormGroup>
<FormRadios v-model="fontSize">
<template #desc>{{ $t('fontSize') }}</template>
<option value="small"><span style="font-size: 14px;">Aa</span></option>
<option :value="null"><span style="font-size: 16px;">Aa</span></option>
<option value="large"><span style="font-size: 18px;">Aa</span></option>
<option value="veryLarge"><span style="font-size: 20px;">Aa</span></option>
</FormRadios>
<FormSelect v-model:value="instanceTicker">
<template #label>{{ $t('instanceTicker') }}</template>
<option value="none">{{ $t('_instanceTicker.none') }}</option>
<option value="remote">{{ $t('_instanceTicker.remote') }}</option>
<option value="always">{{ $t('_instanceTicker.always') }}</option>
</FormSelect>
<FormSelect v-model:value="nsfw">
<template #label>{{ $t('nsfw') }}</template>
<option value="respect">{{ $t('_nsfw.respect') }}</option>
<option value="ignore">{{ $t('_nsfw.ignore') }}</option>
<option value="force">{{ $t('_nsfw.force') }}</option>
</FormSelect>
<FormGroup>
<template #label>{{ $t('defaultNavigationBehaviour') }}</template>
<FormSwitch v-model:value="defaultSideView">{{ $t('openInSideView') }}</FormSwitch>
</FormGroup>
<FormSelect v-model:value="chatOpenBehavior">
<template #label>{{ $t('chatOpenBehavior') }}</template>
<option value="page">{{ $t('showInPage') }}</option>
<option value="window">{{ $t('openInWindow') }}</option>
<option value="popout">{{ $t('popout') }}</option>
</FormSelect>
<FormLink to="/settings/deck">{{ $t('deck') }}</FormLink>
<FormButton @click="cacheClear()" danger>{{ $t('cacheClear') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faImage, faCog, faColumns, faCogs } from '@fortawesome/free-solid-svg-icons';
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 MkRadios from '@/components/ui/radios.vue';
import MkRange from '@/components/ui/range.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormLink from '@/components/form/link.vue';
import FormButton from '@/components/form/button.vue';
import MkLink from '@/components/link.vue';
import { langs } from '@/config';
import { clientDb, set } from '@/db';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
MkSwitch,
MkSelect,
MkRadio,
MkRadios,
MkRange,
MkLink,
FormSwitch,
FormSelect,
FormRadios,
FormBase,
FormGroup,
FormLink,
FormButton,
},
emits: ['info'],
@ -167,11 +168,6 @@ export default defineComponent({
set(value) { this.$store.commit('device/set', { key: 'defaultSideView', value }); }
},
deckNavWindow: {
get() { return this.$store.state.device.deckNavWindow; },
set(value) { this.$store.commit('device/set', { key: 'deckNavWindow', value }); }
},
chatOpenBehavior: {
get() { return this.$store.state.device.chatOpenBehavior; },
set(value) { this.$store.commit('device/set', { key: 'chatOpenBehavior', value }); }
@ -182,20 +178,25 @@ export default defineComponent({
set(value) { this.$store.commit('device/set', { key: 'instanceTicker', value }); }
},
loadRawImages: {
get() { return this.$store.state.device.loadRawImages; },
set(value) { this.$store.commit('device/set', { key: 'loadRawImages', value }); }
},
disableShowingAnimatedImages: {
get() { return this.$store.state.device.disableShowingAnimatedImages; },
set(value) { this.$store.commit('device/set', { key: 'disableShowingAnimatedImages', value }); }
},
nsfw: {
get() { return this.$store.state.device.nsfw; },
set(value) { this.$store.commit('device/set', { key: 'nsfw', value }); }
},
enableInfiniteScroll: {
get() { return this.$store.state.device.enableInfiniteScroll; },
set(value) { this.$store.commit('device/set', { key: 'enableInfiniteScroll', value }); }
},
deckAlwaysShowMainColumn: {
get() { return this.$store.state.device.deckAlwaysShowMainColumn; },
set(value) { this.$store.commit('device/set', { key: 'deckAlwaysShowMainColumn', value }); }
},
deckColumnAlign: {
get() { return this.$store.state.device.deckColumnAlign; },
set(value) { this.$store.commit('device/set', { key: 'deckColumnAlign', value }); }
},
},
watch: {

View file

@ -1,35 +1,36 @@
<template>
<div class="vvcocwet" :class="{ wide: !narrow }" ref="el">
<div class="nav" v-if="!narrow || page == null">
<div class="menu">
<div class="label">{{ $t('basicSettings') }}</div>
<MkA class="item" :class="{ active: page === 'profile' }" replace to="/settings/profile"><Fa :icon="faUser" fixed-width class="icon"/>{{ $t('profile') }}</MkA>
<MkA class="item" :class="{ active: page === 'privacy' }" replace to="/settings/privacy"><Fa :icon="faLockOpen" fixed-width class="icon"/>{{ $t('privacy') }}</MkA>
<MkA class="item" :class="{ active: page === 'reaction' }" replace to="/settings/reaction"><Fa :icon="faLaugh" fixed-width class="icon"/>{{ $t('reaction') }}</MkA>
<MkA class="item" :class="{ active: page === 'notifications' }" replace to="/settings/notifications"><Fa :icon="faBell" fixed-width class="icon"/>{{ $t('notifications') }}</MkA>
<MkA class="item" :class="{ active: page === 'integration' }" replace to="/settings/integration"><Fa :icon="faShareAlt" fixed-width class="icon"/>{{ $t('integration') }}</MkA>
<MkA class="item" :class="{ active: page === 'security' }" replace to="/settings/security"><Fa :icon="faLock" fixed-width class="icon"/>{{ $t('security') }}</MkA>
</div>
<div class="menu">
<div class="label">{{ $t('clientSettings') }}</div>
<MkA class="item" :class="{ active: page === 'general' }" replace to="/settings/general"><Fa :icon="faCogs" fixed-width class="icon"/>{{ $t('general') }}</MkA>
<MkA class="item" :class="{ active: page === 'theme' }" replace to="/settings/theme"><Fa :icon="faPalette" fixed-width class="icon"/>{{ $t('theme') }}</MkA>
<MkA class="item" :class="{ active: page === 'sidebar' }" replace to="/settings/sidebar"><Fa :icon="faListUl" fixed-width class="icon"/>{{ $t('sidebar') }}</MkA>
<MkA class="item" :class="{ active: page === 'sounds' }" replace to="/settings/sounds"><Fa :icon="faMusic" fixed-width class="icon"/>{{ $t('sounds') }}</MkA>
<MkA class="item" :class="{ active: page === 'plugins' }" replace to="/settings/plugins"><Fa :icon="faPlug" fixed-width class="icon"/>{{ $t('plugins') }}</MkA>
</div>
<div class="menu">
<div class="label">{{ $t('otherSettings') }}</div>
<MkA class="item" :class="{ active: page === 'import-export' }" replace to="/settings/import-export"><Fa :icon="faBoxes" fixed-width class="icon"/>{{ $t('importAndExport') }}</MkA>
<MkA class="item" :class="{ active: page === 'mute-block' }" replace to="/settings/mute-block"><Fa :icon="faBan" fixed-width class="icon"/>{{ $t('muteAndBlock') }}</MkA>
<MkA class="item" :class="{ active: page === 'word-mute' }" replace to="/settings/word-mute"><Fa :icon="faCommentSlash" fixed-width class="icon"/>{{ $t('wordMute') }}</MkA>
<MkA class="item" :class="{ active: page === 'api' }" replace to="/settings/api"><Fa :icon="faKey" fixed-width class="icon"/>API</MkA>
<MkA class="item" :class="{ active: page === 'other' }" replace to="/settings/other"><Fa :icon="faEllipsisH" fixed-width class="icon"/>{{ $t('other') }}</MkA>
</div>
<div class="menu">
<button class="_button item" @click="logout">{{ $t('logout') }}</button>
</div>
</div>
<FormBase class="nav" v-if="!narrow || page == null" :force-wide="!narrow">
<FormGroup>
<template #label>{{ $t('basicSettings') }}</template>
<FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><Fa :icon="faUser"/></template>{{ $t('profile') }}</FormLink>
<FormLink :active="page === 'privacy'" replace to="/settings/privacy"><template #icon><Fa :icon="faLockOpen"/></template>{{ $t('privacy') }}</FormLink>
<FormLink :active="page === 'reaction'" replace to="/settings/reaction"><template #icon><Fa :icon="faLaugh"/></template>{{ $t('reaction') }}</FormLink>
<FormLink :active="page === 'notifications'" replace to="/settings/notifications"><template #icon><Fa :icon="faBell"/></template>{{ $t('notifications') }}</FormLink>
<FormLink :active="page === 'email'" replace to="/settings/email"><template #icon><Fa :icon="faEnvelope"/></template>{{ $t('email') }}</FormLink>
<FormLink :active="page === 'integration'" replace to="/settings/integration"><template #icon><Fa :icon="faShareAlt"/></template>{{ $t('integration') }}</FormLink>
<FormLink :active="page === 'security'" replace to="/settings/security"><template #icon><Fa :icon="faLock"/></template>{{ $t('security') }}</FormLink>
</FormGroup>
<FormGroup>
<template #label>{{ $t('clientSettings') }}</template>
<FormLink :active="page === 'general'" replace to="/settings/general"><template #icon><Fa :icon="faCogs"/></template>{{ $t('general') }}</FormLink>
<FormLink :active="page === 'theme'" replace to="/settings/theme"><template #icon><Fa :icon="faPalette"/></template>{{ $t('theme') }}</FormLink>
<FormLink :active="page === 'sidebar'" replace to="/settings/sidebar"><template #icon><Fa :icon="faListUl"/></template>{{ $t('sidebar') }}</FormLink>
<FormLink :active="page === 'sounds'" replace to="/settings/sounds"><template #icon><Fa :icon="faMusic"/></template>{{ $t('sounds') }}</FormLink>
<FormLink :active="page === 'plugins'" replace to="/settings/plugins"><template #icon><Fa :icon="faPlug"/></template>{{ $t('plugins') }}</FormLink>
</FormGroup>
<FormGroup>
<template #label>{{ $t('otherSettings') }}</template>
<FormLink :active="page === 'import-export'" replace to="/settings/import-export"><template #icon><Fa :icon="faBoxes"/></template>{{ $t('importAndExport') }}</FormLink>
<FormLink :active="page === 'mute-block'" replace to="/settings/mute-block"><template #icon><Fa :icon="faBan"/></template>{{ $t('muteAndBlock') }}</FormLink>
<FormLink :active="page === 'word-mute'" replace to="/settings/word-mute"><template #icon><Fa :icon="faCommentSlash"/></template>{{ $t('wordMute') }}</FormLink>
<FormLink :active="page === 'api'" replace to="/settings/api"><template #icon><Fa :icon="faKey"/></template>API</FormLink>
<FormLink :active="page === 'other'" replace to="/settings/other"><template #icon><Fa :icon="faEllipsisH"/></template>{{ $t('other') }}</FormLink>
</FormGroup>
<FormGroup>
<FormButton @click="logout" danger>{{ $t('logout') }}</FormButton>
</FormGroup>
</FormBase>
<div class="main">
<component :is="component" @info="onInfo"/>
</div>
@ -37,13 +38,25 @@
</template>
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent, onMounted, ref } from 'vue';
import { computed, defineAsyncComponent, defineComponent, nextTick, onMounted, ref, watch } from 'vue';
import { faCog, faPalette, faPlug, faUser, faListUl, faLock, faCommentSlash, faMusic, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes } from '@fortawesome/free-solid-svg-icons';
import { faLaugh, faBell } from '@fortawesome/free-regular-svg-icons';
import { faLaugh, faBell, faEnvelope } from '@fortawesome/free-regular-svg-icons';
import { store } from '@/store';
import { i18n } from '@/i18n';
import FormLink from '@/components/form/link.vue';
import FormGroup from '@/components/form/group.vue';
import FormBase from '@/components/form/base.vue';
import FormButton from '@/components/form/button.vue';
import { scroll } from '../../scripts/scroll';
export default defineComponent({
components: {
FormBase,
FormLink,
FormGroup,
FormButton,
},
props: {
page: {
type: String,
@ -72,21 +85,35 @@ export default defineComponent({
case 'word-mute': return defineAsyncComponent(() => import('./word-mute.vue'));
case 'integration': return defineAsyncComponent(() => import('./integration.vue'));
case 'security': return defineAsyncComponent(() => import('./security.vue'));
case '2fa': return defineAsyncComponent(() => import('./2fa.vue'));
case 'api': return defineAsyncComponent(() => import('./api.vue'));
case 'apps': return defineAsyncComponent(() => import('./apps.vue'));
case 'other': return defineAsyncComponent(() => import('./other.vue'));
case 'general': return defineAsyncComponent(() => import('./general.vue'));
case 'email': return defineAsyncComponent(() => import('./email.vue'));
case 'email/address': return defineAsyncComponent(() => import('./email-address.vue'));
case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
case 'theme/install': return defineAsyncComponent(() => import('./theme.install.vue'));
case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue'));
case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue'));
case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
case 'plugins': return defineAsyncComponent(() => import('./plugins.vue'));
case 'import-export': return defineAsyncComponent(() => import('./import-export.vue'));
case 'account-info': return defineAsyncComponent(() => import('./account-info.vue'));
case 'regedit': return defineAsyncComponent(() => import('./regedit.vue'));
default: return null;
}
});
watch(component, () => {
nextTick(() => {
scroll(el.value, 0);
});
});
onMounted(() => {
narrow.value = el.value.offsetWidth < 650;
narrow.value = el.value.offsetWidth < 1025;
});
return {
@ -100,7 +127,7 @@ export default defineComponent({
store.dispatch('logout');
location.href = '/';
},
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes,
faPalette, faPlug, faUser, faListUl, faLock, faLaugh, faCommentSlash, faMusic, faBell, faCogs, faEllipsisH, faBan, faShareAlt, faLockOpen, faKey, faBoxes, faEnvelope,
};
},
});
@ -108,63 +135,19 @@ export default defineComponent({
<style lang="scss" scoped>
.vvcocwet {
> .nav {
> .menu {
margin: 16px 0;
> .label {
padding: 8px 32px;
font-size: 80%;
opacity: 0.7;
}
> .item {
display: block;
width: 100%;
box-sizing: border-box;
padding: 0 32px;
line-height: 40px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
//background: var(--panel);
//border-bottom: solid 1px var(--divider);
transition: padding 0.2s ease, color 0.1s ease;
&:first-of-type {
//border-top: solid 1px var(--divider);
}
&.active {
color: var(--accent);
padding-left: 42px;
}
&:hover {
text-decoration: none;
padding-left: 42px;
}
> .icon {
margin-right: 0.5em;
}
}
}
}
&.wide {
display: flex;
max-width: 1100px;
margin: 0 auto;
> .nav {
width: 30%;
max-width: 300px;
font-size: 0.95em;
border-right: solid 1px var(--divider);
width: 32%;
box-sizing: border-box;
border-right: solid 0.5px var(--divider);
}
> .main {
flex: 1;
padding: 32px;
--baseContentWidth: 100%;
::v-deep(._section) {

View file

@ -1,29 +1,31 @@
<template>
<div>
<div class="_section">
<MkButton full primary @click="configure"><Fa :icon="faCog"/> {{ $t('notificationSetting') }}</MkButton>
</div>
<div class="_section">
<MkButton full @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</MkButton>
<MkButton full @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</MkButton>
<MkButton full @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</MkButton>
</div>
</div>
<FormBase>
<FormLink @click="configure">{{ $t('notificationSetting') }}</FormLink>
<FormGroup>
<FormButton @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</FormButton>
<FormButton @click="readAllUnreadNotes">{{ $t('markAsReadAllUnreadNotes') }}</FormButton>
<FormButton @click="readAllMessagingMessages">{{ $t('markAsReadAllTalkMessages') }}</FormButton>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCog } from '@fortawesome/free-solid-svg-icons';
import { faBell } from '@fortawesome/free-regular-svg-icons';
import MkButton from '@/components/ui/button.vue';
import MkSwitch from '@/components/ui/switch.vue';
import FormButton from '@/components/form/button.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import { notificationTypes } from '../../../types';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
MkSwitch,
FormBase,
FormLink,
FormButton,
FormGroup,
},
emits: ['info'],

View file

@ -1,40 +1,43 @@
<template>
<div>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $t('showFeaturedNotesInTimeline') }}
</MkSwitch>
</div>
</div>
</div>
<div class="_section">
<MkSwitch v-model:value="debug" @update:value="changeDebug">
<FormBase>
<FormSwitch :value="$store.state.i.injectFeaturedNote" @update:value="onChangeInjectFeaturedNote">
{{ $t('showFeaturedNotesInTimeline') }}
</FormSwitch>
<FormLink to="/settings/account-info">{{ $t('accountInfo') }}</FormLink>
<FormGroup>
<FormSwitch v-model:value="debug" @update:value="changeDebug">
DEBUG MODE
</MkSwitch>
<div v-if="debug">
<MkA to="/settings/regedit">RegEdit</MkA>
<MkButton @click="taskmanager">Task Manager</MkButton>
</div>
</div>
</div>
</FormSwitch>
<template v-if="debug">
<FormLink to="/settings/regedit">RegEdit</FormLink>
<FormButton @click="taskmanager">Task Manager</FormButton>
</template>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
import MkSelect from '@/components/ui/select.vue';
import MkSwitch from '@/components/ui/switch.vue';
import MkButton from '@/components/ui/button.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormLink from '@/components/form/link.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import * as os from '@/os';
import { debug } from '@/config';
export default defineComponent({
components: {
MkSelect,
MkSwitch,
MkButton,
FormBase,
FormSelect,
FormSwitch,
FormButton,
FormLink,
FormGroup,
},
emits: ['info'],

View file

@ -1,36 +1,43 @@
<template>
<div class="_section">
<div class="_card">
<div class="_content">
<MkSwitch v-model:value="isLocked" @update:value="save()">{{ $t('makeFollowManuallyApprove') }}</MkSwitch>
<MkSwitch v-model:value="autoAcceptFollowed" v-if="isLocked" @update:value="save()">{{ $t('autoAcceptFollowed') }}</MkSwitch>
</div>
<div class="_content">
<MkSwitch v-model:value="rememberNoteVisibility" @update:value="save()">{{ $t('rememberNoteVisibility') }}</MkSwitch>
<MkSelect v-model:value="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>
</MkSelect>
<MkSwitch v-model:value="defaultNoteLocalOnly" v-if="!rememberNoteVisibility">{{ $t('_visibility.localOnly') }}</MkSwitch>
</div>
</div>
</div>
<FormBase>
<FormGroup>
<FormSwitch v-model:value="isLocked" @update:value="save()">{{ $t('makeFollowManuallyApprove') }}</FormSwitch>
<FormSwitch v-model:value="autoAcceptFollowed" :disabled="!isLocked" @update:value="save()">{{ $t('autoAcceptFollowed') }}</FormSwitch>
<template #caption>{{ $t('lockedAccountInfo') }}</template>
</FormGroup>
<FormSwitch v-model:value="noCrawle" @update:value="save()">
{{ $t('noCrawle') }}
<template #desc>{{ $t('noCrawleDescription') }}</template>
</FormSwitch>
<FormSwitch v-model:value="rememberNoteVisibility" @update:value="save()">{{ $t('rememberNoteVisibility') }}</FormSwitch>
<FormGroup v-if="!rememberNoteVisibility">
<template #label>{{ $t('defaultNoteVisibility') }}</template>
<FormSelect v-model:value="defaultNoteVisibility">
<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>
</FormSelect>
<FormSwitch v-model:value="defaultNoteLocalOnly">{{ $t('_visibility.localOnly') }}</FormSwitch>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faLockOpen } from '@fortawesome/free-solid-svg-icons';
import MkSelect from '@/components/ui/select.vue';
import MkSwitch from '@/components/ui/switch.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkSelect,
MkSwitch,
FormBase,
FormSelect,
FormGroup,
FormSwitch,
},
emits: ['info'],
@ -43,6 +50,7 @@ export default defineComponent({
},
isLocked: false,
autoAcceptFollowed: false,
noCrawle: false,
}
},
@ -66,6 +74,7 @@ export default defineComponent({
created() {
this.isLocked = this.$store.state.i.isLocked;
this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed;
this.noCrawle = this.$store.state.i.noCrawle;
},
mounted() {
@ -77,6 +86,7 @@ export default defineComponent({
os.api('i/update', {
isLocked: !!this.isLocked,
autoAcceptFollowed: !!this.autoAcceptFollowed,
noCrawle: !!this.noCrawle,
});
}
}

View file

@ -1,79 +1,67 @@
<template>
<div class="_section">
<div 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">
<MkAvatar class="avatar" :user="$store.state.i" :disable-preview="true" :disable-link="true" @click.stop="changeAvatar"/>
</div>
<MkInput v-model:value="name" :max="30">
<span>{{ $t('_profile.name') }}</span>
</MkInput>
<MkTextarea v-model:value="description" :max="500">
<span>{{ $t('_profile.description') }}</span>
<template #desc>{{ $t('_profile.youCanIncludeHashtags') }}</template>
</MkTextarea>
<MkInput v-model:value="location">
<span>{{ $t('location') }}</span>
<template #prefix><Fa :icon="faMapMarkerAlt"/></template>
</MkInput>
<MkInput v-model:value="birthday" type="date">
<template #title>{{ $t('birthday') }}</template>
<template #prefix><Fa :icon="faBirthdayCake"/></template>
</MkInput>
<details class="fields">
<summary>{{ $t('_profile.metadata') }}</summary>
<div class="row">
<MkInput v-model:value="fieldName0">{{ $t('_profile.metadataLabel') }}</MkInput>
<MkInput v-model:value="fieldValue0">{{ $t('_profile.metadataContent') }}</MkInput>
</div>
<div class="row">
<MkInput v-model:value="fieldName1">{{ $t('_profile.metadataLabel') }}</MkInput>
<MkInput v-model:value="fieldValue1">{{ $t('_profile.metadataContent') }}</MkInput>
</div>
<div class="row">
<MkInput v-model:value="fieldName2">{{ $t('_profile.metadataLabel') }}</MkInput>
<MkInput v-model:value="fieldValue2">{{ $t('_profile.metadataContent') }}</MkInput>
</div>
<div class="row">
<MkInput v-model:value="fieldName3">{{ $t('_profile.metadataLabel') }}</MkInput>
<MkInput v-model:value="fieldValue3">{{ $t('_profile.metadataContent') }}</MkInput>
</div>
</details>
<MkSwitch v-model:value="isBot">{{ $t('flagAsBot') }}</MkSwitch>
<MkSwitch v-model:value="isCat">{{ $t('flagAsCat') }}</MkSwitch>
</div>
<div class="_footer">
<MkButton @click="save(true)" primary><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
</div>
<FormBase class="llvierxe">
<div class="header _formItem" :style="{ backgroundImage: $store.state.i.bannerUrl ? `url(${ $store.state.i.bannerUrl })` : null }" @click="changeBanner">
<MkAvatar class="avatar" :user="$store.state.i" :disable-preview="true" :disable-link="true" @click.stop="changeAvatar"/>
</div>
</div>
<FormInput v-model:value="name" :max="30">
<span>{{ $t('_profile.name') }}</span>
</FormInput>
<FormTextarea v-model:value="description" :max="500">
<span>{{ $t('_profile.description') }}</span>
<template #desc>{{ $t('_profile.youCanIncludeHashtags') }}</template>
</FormTextarea>
<FormInput v-model:value="location">
<span>{{ $t('location') }}</span>
<template #prefix><Fa :icon="faMapMarkerAlt"/></template>
</FormInput>
<FormInput v-model:value="birthday" type="date">
<span>{{ $t('birthday') }}</span>
<template #prefix><Fa :icon="faBirthdayCake"/></template>
</FormInput>
<FormGroup>
<FormButton @click="editMetadata" primary>{{ $t('_profile.metadataEdit') }}</FormButton>
<template #caption>{{ $t('_profile.metadataDescription') }}</template>
</FormGroup>
<FormSwitch v-model:value="isCat">{{ $t('flagAsCat') }}<template #desc>{{ $t('flagAsCatDescription') }}</template></FormSwitch>
<FormSwitch v-model:value="isBot">{{ $t('flagAsBot') }}<template #desc>{{ $t('flagAsBotDescription') }}</template></FormSwitch>
<FormSwitch v-model:value="alwaysMarkNsfw">{{ $t('alwaysMarkSensitive') }}</FormSwitch>
<FormButton @click="save(true)" primary><Fa :icon="faSave"/> {{ $t('save') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } 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 FormButton from '@/components/form/button.vue';
import FormInput from '@/components/form/input.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormTuple from '@/components/form/tuple.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import { host } from '@/config';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
MkInput,
MkTextarea,
MkSwitch,
FormButton,
FormInput,
FormTextarea,
FormSwitch,
FormTuple,
FormBase,
FormGroup,
},
emits: ['info'],
@ -101,6 +89,7 @@ export default defineComponent({
bannerId: null,
isBot: false,
isCat: false,
alwaysMarkNsfw: false,
saving: false,
faSave, faUnlockAlt, faCogs, faUser, faMapMarkerAlt, faBirthdayCake
}
@ -115,6 +104,7 @@ export default defineComponent({
this.bannerId = this.$store.state.i.bannerId;
this.isBot = this.$store.state.i.isBot;
this.isCat = this.$store.state.i.isCat;
this.alwaysMarkNsfw = this.$store.state.i.alwaysMarkNsfw;
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;
@ -147,7 +137,60 @@ export default defineComponent({
});
},
save(notify) {
async editMetadata() {
const { canceled, result } = await os.form(this.$t('_profile.metadata'), {
fieldName0: {
type: 'string',
label: this.$t('_profile.metadataLabel') + ' 1',
default: this.fieldName0,
},
fieldValue0: {
type: 'string',
label: this.$t('_profile.metadataContent') + ' 1',
default: this.fieldValue0,
},
fieldName1: {
type: 'string',
label: this.$t('_profile.metadataLabel') + ' 2',
default: this.fieldName1,
},
fieldValue1: {
type: 'string',
label: this.$t('_profile.metadataContent') + ' 2',
default: this.fieldValue1,
},
fieldName2: {
type: 'string',
label: this.$t('_profile.metadataLabel') + ' 3',
default: this.fieldName2,
},
fieldValue2: {
type: 'string',
label: this.$t('_profile.metadataContent') + ' 3',
default: this.fieldValue2,
},
fieldName3: {
type: 'string',
label: this.$t('_profile.metadataLabel') + ' 4',
default: this.fieldName3,
},
fieldValue3: {
type: 'string',
label: this.$t('_profile.metadataContent') + ' 4',
default: this.fieldValue3,
},
});
if (canceled) return;
this.fieldName0 = result.fieldName0;
this.fieldValue0 = result.fieldValue0;
this.fieldName1 = result.fieldName1;
this.fieldValue1 = result.fieldValue1;
this.fieldName2 = result.fieldName2;
this.fieldValue2 = result.fieldValue2;
this.fieldName3 = result.fieldName3;
this.fieldValue3 = result.fieldValue3;
const fields = [
{ name: this.fieldName0, value: this.fieldValue0 },
{ name: this.fieldName1, value: this.fieldValue1 },
@ -155,6 +198,19 @@ export default defineComponent({
{ name: this.fieldName3, value: this.fieldValue3 },
];
os.api('i/update', {
fields,
}).then(i => {
os.success();
}).catch(err => {
os.dialog({
type: 'error',
text: err.id
});
});
},
save(notify) {
this.saving = true;
os.api('i/update', {
@ -162,9 +218,9 @@ export default defineComponent({
description: this.description || null,
location: this.location || null,
birthday: this.birthday || null,
fields,
isBot: !!this.isBot,
isCat: !!this.isCat,
alwaysMarkNsfw: !!this.alwaysMarkNsfw,
}).then(i => {
this.saving = false;
this.$store.state.i.avatarId = i.avatarId;
@ -189,41 +245,29 @@ export default defineComponent({
<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;
> .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;
> .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;
}
}
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.5);
}
}
}

View file

@ -1,9 +1,8 @@
<template>
<div class="_section">
<div class="_card">
<div class="_title"><Fa :icon="faLaugh"/> {{ $t('reaction') }}</div>
<div class="_content">
<div class="_caption" style="padding: 0 8px 8px 8px;">{{ $t('reactionSettingDescription') }}</div>
<FormBase>
<div class="_formItem">
<div class="_formLabel">{{ $t('reactionSettingDescription') }}</div>
<div class="_formPanel">
<XDraggable class="zoaiodol" :list="reactions" animation="150" delay="100" delay-on-touch-only="true">
<button class="_button item" v-for="reaction in reactions" :key="reaction" @click="remove(reaction, $event)">
<MkEmoji :emoji="reaction" :normal="true"/>
@ -12,26 +11,25 @@
<button>a</button>
</template>
</XDraggable>
<div class="_caption" style="padding: 8px;">{{ $t('reactionSettingDescription2') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></div>
<MkRadios v-model="reactionPickerWidth">
<template #desc>{{ $t('width') }}</template>
<option :value="1">{{ $t('small') }}</option>
<option :value="2">{{ $t('medium') }}</option>
<option :value="3">{{ $t('large') }}</option>
</MkRadios>
<MkRadios v-model="reactionPickerHeight">
<template #desc>{{ $t('height') }}</template>
<option :value="1">{{ $t('small') }}</option>
<option :value="2">{{ $t('medium') }}</option>
<option :value="3">{{ $t('large') }}</option>
</MkRadios>
</div>
<div class="_footer">
<MkButton inline @click="preview"><Fa :icon="faEye"/> {{ $t('preview') }}</MkButton>
<MkButton inline @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</MkButton>
</div>
<div class="_formCaption">{{ $t('reactionSettingDescription2') }} <button class="_textButton" @click="chooseEmoji">{{ $t('chooseEmoji') }}</button></div>
</div>
</div>
<FormRadios v-model="reactionPickerWidth">
<template #desc>{{ $t('width') }}</template>
<option :value="1">{{ $t('small') }}</option>
<option :value="2">{{ $t('medium') }}</option>
<option :value="3">{{ $t('large') }}</option>
</FormRadios>
<FormRadios v-model="reactionPickerHeight">
<template #desc>{{ $t('height') }}</template>
<option :value="1">{{ $t('small') }}</option>
<option :value="2">{{ $t('medium') }}</option>
<option :value="3">{{ $t('large') }}</option>
</FormRadios>
<FormButton @click="preview"><Fa :icon="faEye"/> {{ $t('preview') }}</FormButton>
<FormButton danger @click="setDefault"><Fa :icon="faUndo"/> {{ $t('default') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
@ -39,20 +37,19 @@ import { defineComponent } from 'vue';
import { faLaugh, faSave, faEye } from '@fortawesome/free-regular-svg-icons';
import { faUndo } from '@fortawesome/free-solid-svg-icons';
import { VueDraggableNext } from 'vue-draggable-next';
import MkInput from '@/components/ui/input.vue';
import MkButton from '@/components/ui/button.vue';
import MkSwitch from '@/components/ui/switch.vue';
import MkRadios from '@/components/ui/radios.vue';
import { emojiRegexWithCustom } from '../../../misc/emoji-regex';
import FormInput from '@/components/form/input.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormButton from '@/components/form/button.vue';
import { defaultSettings } from '@/store';
import * as os from '@/os';
export default defineComponent({
components: {
MkInput,
MkButton,
MkSwitch,
MkRadios,
FormInput,
FormButton,
FormBase,
FormRadios,
XDraggable: VueDraggableNext,
},
@ -62,7 +59,11 @@ export default defineComponent({
return {
INFO: {
title: this.$t('reaction'),
icon: faLaugh
icon: faLaugh,
action: {
icon: faEye,
handler: this.preview
}
},
reactions: JSON.parse(JSON.stringify(this.$store.state.settings.reactions)),
faLaugh, faSave, faEye, faUndo
@ -144,8 +145,6 @@ export default defineComponent({
<style lang="scss" scoped>
.zoaiodol {
border: solid 1px var(--divider);
border-radius: var(--radius);
padding: 16px;
> .item {

View file

@ -1,29 +1,45 @@
<template>
<div>
<div class="_section">
<X2fa/>
</div>
<div class="_section">
<MkButton primary @click="change()" full>{{ $t('changePassword') }}</MkButton>
</div>
<div class="_section">
<MkButton class="_vMargin" primary @click="regenerateToken" full><Fa :icon="faSyncAlt"/> {{ $t('regenerateLoginToken') }}</MkButton>
<div class="_caption _vMargin" style="padding: 0 6px;">{{ $t('regenerateLoginTokenDescription') }}</div>
</div>
</div>
<FormBase>
<X2fa/>
<FormLink to="/settings/2fa"><template #icon><Fa :icon="faMobileAlt"/></template>{{ $t('twoStepAuthentication') }}</FormLink>
<FormButton primary @click="change()">{{ $t('changePassword') }}</FormButton>
<FormPagination :pagination="pagination">
<template #label>{{ $t('signinHistory') }}</template>
<template #default="{items}">
<div class="_formPanel timnmucd" v-for="item in items" :key="item.id">
<header>
<Fa class="icon succ" :icon="faCheck" v-if="item.success"/>
<Fa class="icon fail" :icon="faTimesCircle" v-else/>
<code class="ip _monospace">{{ item.ip }}</code>
<MkTime :time="item.createdAt" class="time"/>
</header>
</div>
</template>
</FormPagination>
<FormGroup>
<FormButton danger @click="regenerateToken"><Fa :icon="faSyncAlt"/> {{ $t('regenerateLoginToken') }}</FormButton>
<template #caption>{{ $t('regenerateLoginTokenDescription') }}</template>
</FormGroup>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faLock, faSyncAlt } from '@fortawesome/free-solid-svg-icons';
import MkButton from '@/components/ui/button.vue';
import X2fa from './security.2fa.vue';
import { faCheck, faTimesCircle, faLock, faSyncAlt, faMobileAlt } from '@fortawesome/free-solid-svg-icons';
import FormBase from '@/components/form/base.vue';
import FormLink from '@/components/form/link.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import FormPagination from '@/components/form/pagination.vue';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
X2fa,
FormBase,
FormLink,
FormButton,
FormPagination,
FormGroup,
},
emits: ['info'],
@ -34,7 +50,11 @@ export default defineComponent({
title: this.$t('security'),
icon: faLock
},
faLock, faSyncAlt
pagination: {
endpoint: 'i/signin-history',
limit: 5,
},
faLock, faSyncAlt, faCheck, faTimesCircle, faMobileAlt,
}
},
@ -98,3 +118,32 @@ export default defineComponent({
}
});
</script>
<style lang="scss" scoped>
.timnmucd {
padding: 16px;
> header {
display: flex;
align-items: center;
> .icon {
width: 1em;
margin-right: 0.75em;
&.succ {
color: var(--success);
}
&.fail {
color: var(--error);
}
}
> .time {
margin-left: auto;
opacity: 0.7;
}
}
}
</style>

View file

@ -1,41 +1,41 @@
<template>
<div class="_section">
<div class="_card">
<div class="_content">
<MkTextarea v-model:value="items" tall>
<span>{{ $t('sidebar') }}</span>
<template #desc><button class="_textButton" @click="addItem">{{ $t('addItem') }}</button></template>
</MkTextarea>
</div>
<div class="_content">
<div>{{ $t('display') }}</div>
<MkRadio v-model="sidebarDisplay" value="full">{{ $t('_sidebar.full') }}</MkRadio>
<MkRadio v-model="sidebarDisplay" value="icon">{{ $t('_sidebar.icon') }}</MkRadio>
<!-- <MkRadio v-model="sidebarDisplay" value="hide" disabled>{{ $t('_sidebar.hide') }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
</div>
<div class="_footer">
<MkButton inline @click="save()" primary><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
<MkButton inline @click="reset()"><Fa :icon="faRedo"/> {{ $t('default') }}</MkButton>
</div>
</div>
</div>
<FormBase>
<FormTextarea v-model:value="items" tall>
<span>{{ $t('sidebar') }}</span>
<template #desc><button class="_textButton" @click="addItem">{{ $t('addItem') }}</button></template>
</FormTextarea>
<FormRadios v-model="sidebarDisplay">
<template #desc>{{ $t('display') }}</template>
<option value="full">{{ $t('_sidebar.full') }}</option>
<option value="icon">{{ $t('_sidebar.icon') }}</option>
<!-- <MkRadio v-model="sidebarDisplay" value="hide" disabled>{{ $t('_sidebar.hide') }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
</FormRadios>
<FormButton @click="save()" primary><Fa :icon="faSave"/> {{ $t('save') }}</FormButton>
<FormButton @click="reset()" danger><Fa :icon="faRedo"/> {{ $t('default') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faListUl, faSave, faRedo } from '@fortawesome/free-solid-svg-icons';
import MkButton from '@/components/ui/button.vue';
import MkTextarea from '@/components/ui/textarea.vue';
import MkRadio from '@/components/ui/radio.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormButton from '@/components/form/button.vue';
import { defaultDeviceUserSettings } from '@/store';
import * as os from '@/os';
import { sidebarDef } from '@/sidebar';
export default defineComponent({
components: {
MkButton,
MkTextarea,
MkRadio,
FormBase,
FormButton,
FormTextarea,
FormRadios,
},
emits: ['info'],
@ -102,7 +102,3 @@ export default defineComponent({
},
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -1,62 +1,35 @@
<template>
<div class="_section">
<div class="_card">
<div class="_title"><Fa :icon="faMusic"/> {{ $t('sounds') }}</div>
<div class="_content">
<MkRange v-model:value="sfxVolume" :min="0" :max="1" :step="0.1">
<Fa slot="icon" :icon="volumeIcon"/>
<span slot="title">{{ $t('volume') }}</span>
</MkRange>
</div>
<div class="_content">
<MkSelect v-model:value="sfxNote">
<template #label>{{ $t('_sfx.note') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxNote)" v-if="sfxNote"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
<MkSelect v-model:value="sfxNoteMy">
<template #label>{{ $t('_sfx.noteMy') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxNoteMy)" v-if="sfxNoteMy"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
<MkSelect v-model:value="sfxNotification">
<template #label>{{ $t('_sfx.notification') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxNotification)" v-if="sfxNotification"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
<MkSelect v-model:value="sfxChat">
<template #label>{{ $t('_sfx.chat') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxChat)" v-if="sfxChat"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
<MkSelect v-model:value="sfxChatBg">
<template #label>{{ $t('_sfx.chatBg') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxChatBg)" v-if="sfxChatBg"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
<MkSelect v-model:value="sfxAntenna">
<template #label>{{ $t('_sfx.antenna') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxAntenna)" v-if="sfxAntenna"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
<MkSelect v-model:value="sfxChannel">
<template #label>{{ $t('_sfx.channel') }}</template>
<option v-for="sound in sounds" :value="sound" :key="sound">{{ sound || $t('none') }}</option>
<template #text><button class="_textButton" @click="listen(sfxChannel)" v-if="sfxChannel"><Fa :icon="faPlay"/> {{ $t('listen') }}</button></template>
</MkSelect>
</div>
</div>
</div>
<FormBase>
<FormRange v-model:value="masterVolume" :min="0" :max="1" :step="0.05">
<template #label><Fa :icon="volumeIcon" :key="volumeIcon"/> {{ $t('masterVolume') }}</template>
</FormRange>
<FormGroup>
<template #label>{{ $t('sounds') }}</template>
<FormButton v-for="type in Object.keys(sounds)" :key="type" :center="false" @click="edit(type)">
{{ $t('_sfx.' + type) }}
<template #suffix>{{ sounds[type].type || $t('none') }}</template>
<template #suffixIcon><Fa :icon="faChevronDown"/></template>
</FormButton>
</FormGroup>
<FormButton @click="reset()" danger><Fa :icon="faRedo"/> {{ $t('default') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
import MkSelect from '@/components/ui/select.vue';
import MkRange from '@/components/ui/range.vue';
import { faMusic, faPlay, faVolumeUp, faVolumeMute, faChevronDown, faRedo } from '@fortawesome/free-solid-svg-icons';
import FormRange from '@/components/form/range.vue';
import FormSelect from '@/components/form/select.vue';
import FormBase from '@/components/form/base.vue';
import FormButton from '@/components/form/button.vue';
import FormGroup from '@/components/form/group.vue';
import * as os from '@/os';
import { device, defaultDeviceSettings } from '@/cold-storage';
import { playFile } from '@/scripts/sound';
const sounds = [
const soundsTypes = [
null,
'syuilo/up',
'syuilo/down',
@ -73,6 +46,8 @@ const sounds = [
'syuilo/square-pico',
'syuilo/reverved',
'syuilo/ryukyu',
'syuilo/kick',
'syuilo/snare',
'aisha/1',
'aisha/2',
'aisha/3',
@ -82,71 +57,98 @@ const sounds = [
export default defineComponent({
components: {
MkSelect,
MkRange,
FormSelect,
FormButton,
FormBase,
FormRange,
FormGroup,
},
emits: ['info'],
data() {
return {
sounds,
faMusic, faPlay, faVolumeUp, faVolumeMute,
INFO: {
title: this.$t('sounds'),
icon: faMusic
},
sounds: {},
faMusic, faPlay, faVolumeUp, faVolumeMute, faChevronDown, faRedo,
}
},
computed: {
sfxVolume: {
get() { return this.$store.state.device.sfxVolume; },
set(value) { this.$store.commit('device/set', { key: 'sfxVolume', value: parseFloat(value, 10) }); }
masterVolume: { // TODO: ()computed使
get() { return device.get('sound_masterVolume'); },
set(value) { device.set('sound_masterVolume', value); }
},
sfxNote: {
get() { return this.$store.state.device.sfxNote; },
set(value) { this.$store.commit('device/set', { key: 'sfxNote', value }); }
},
sfxNoteMy: {
get() { return this.$store.state.device.sfxNoteMy; },
set(value) { this.$store.commit('device/set', { key: 'sfxNoteMy', value }); }
},
sfxNotification: {
get() { return this.$store.state.device.sfxNotification; },
set(value) { this.$store.commit('device/set', { key: 'sfxNotification', value }); }
},
sfxChat: {
get() { return this.$store.state.device.sfxChat; },
set(value) { this.$store.commit('device/set', { key: 'sfxChat', value }); }
},
sfxChatBg: {
get() { return this.$store.state.device.sfxChatBg; },
set(value) { this.$store.commit('device/set', { key: 'sfxChatBg', value }); }
},
sfxAntenna: {
get() { return this.$store.state.device.sfxAntenna; },
set(value) { this.$store.commit('device/set', { key: 'sfxAntenna', value }); }
},
sfxChannel: {
get() { return this.$store.state.device.sfxChannel; },
set(value) { this.$store.commit('device/set', { key: 'sfxChannel', value }); }
},
volumeIcon: {
get() {
return this.sfxVolume === 0 ? faVolumeMute : faVolumeUp;
}
volumeIcon() {
return this.masterVolume === 0 ? faVolumeMute : faVolumeUp;
}
},
created() {
this.sounds.note = device.get('sound_note');
this.sounds.noteMy = device.get('sound_noteMy');
this.sounds.notification = device.get('sound_notification');
this.sounds.chat = device.get('sound_chat');
this.sounds.chatBg = device.get('sound_chatBg');
this.sounds.antenna = device.get('sound_antenna');
this.sounds.channel = device.get('sound_channel');
this.sounds.reversiPutBlack = device.get('sound_reversiPutBlack');
this.sounds.reversiPutWhite = device.get('sound_reversiPutWhite');
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
listen(sound) {
const audio = new Audio(`/assets/sounds/${sound}.mp3`);
audio.volume = this.$store.state.device.sfxVolume;
audio.play();
async edit(type) {
const { canceled, result } = await os.form(this.$t('_sfx.' + type), {
type: {
type: 'enum',
enum: soundsTypes.map(x => ({
value: x,
label: x == null ? this.$t('none') : x,
})),
label: this.$t('sound'),
default: this.sounds[type].type,
},
volume: {
type: 'range',
mim: 0,
max: 1,
step: 0.05,
label: this.$t('volume'),
default: this.sounds[type].volume
},
listen: {
type: 'button',
content: this.$t('listen'),
action: (_, values) => {
playFile(values.type, values.volume);
}
}
});
if (canceled) return;
const v = {
type: result.type,
volume: result.volume,
};
device.set('sound_' + type, v);
this.sounds[type] = v;
},
reset() {
for (const sound of Object.keys(this.sounds)) {
const v = defaultDeviceSettings['sound_' + sound];
device.set('sound_' + sound, v);
this.sounds[sound] = v;
}
}
}
});
</script>

View file

@ -0,0 +1,106 @@
<template>
<FormBase>
<FormGroup>
<FormTextarea v-model:value="installThemeCode">
<span>{{ $t('_theme.code') }}</span>
</FormTextarea>
<FormButton @click="() => preview(installThemeCode)" :disabled="installThemeCode == null" inline><Fa :icon="faEye"/> {{ $t('preview') }}</FormButton>
</FormGroup>
<FormButton @click="() => install(installThemeCode)" :disabled="installThemeCode == null" primary inline><Fa :icon="faCheck"/> {{ $t('install') }}</FormButton>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import FormTextarea from '@/components/form/textarea.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormLink from '@/components/form/link.vue';
import FormButton from '@/components/form/button.vue';
import { applyTheme, validateTheme } from '@/scripts/theme';
import * as os from '@/os';
export default defineComponent({
components: {
FormTextarea,
FormSelect,
FormRadios,
FormBase,
FormGroup,
FormLink,
FormButton,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('_theme.install'),
icon: faDownload
},
installThemeCode: null,
faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye
}
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
parseThemeCode(code) {
let theme;
try {
theme = JSON5.parse(code);
} catch (e) {
os.dialog({
type: 'error',
text: this.$t('_theme.invalid')
});
return false;
}
if (!validateTheme(theme)) {
os.dialog({
type: 'error',
text: this.$t('_theme.invalid')
});
return false;
}
if (this.$store.state.device.themes.some(t => t.id === theme.id)) {
os.dialog({
type: 'info',
text: this.$t('_theme.alreadyInstalled')
});
return false;
}
return theme;
},
preview(code) {
const theme = this.parseThemeCode(code);
if (theme) applyTheme(theme, false);
},
install(code) {
const theme = this.parseThemeCode(code);
if (!theme) return;
const themes = this.$store.state.device.themes.concat(theme);
this.$store.commit('device/set', {
key: 'themes', value: themes
});
os.dialog({
type: 'success',
text: this.$t('_theme.installed', { name: theme.name })
});
},
}
});
</script>

View file

@ -0,0 +1,103 @@
<template>
<FormBase>
<FormSelect v-model:value="selectedThemeId">
<template #label>{{ $t('installedThemes') }}</template>
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
<optgroup :label="$t('builtinThemes')">
<option v-for="x in builtinThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
<template v-if="selectedTheme">
<FormInput readonly :value="selectedTheme.author">
<span>{{ $t('author') }}</span>
</FormInput>
<FormTextarea readonly tall :value="selectedThemeCode">
<span>{{ $t('_theme.code') }}</span>
<template #desc><button @click="copyThemeCode()" class="_textButton">{{ $t('copy') }}</button></template>
</FormTextarea>
<FormButton @click="uninstall()" danger v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><Fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</FormButton>
</template>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import FormTextarea from '@/components/form/textarea.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormInput from '@/components/form/input.vue';
import FormButton from '@/components/form/button.vue';
import { Theme, builtinThemes } from '@/scripts/theme';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os';
export default defineComponent({
components: {
FormTextarea,
FormSelect,
FormRadios,
FormBase,
FormGroup,
FormInput,
FormButton,
},
emits: ['info'],
data() {
return {
INFO: {
title: this.$t('_theme.manage'),
icon: faFolderOpen
},
builtinThemes,
selectedThemeId: null,
faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye
}
},
computed: {
themes(): Theme[] {
return builtinThemes.concat(this.$store.state.device.themes);
},
installedThemes(): Theme[] {
return this.$store.state.device.themes;
},
selectedTheme() {
if (this.selectedThemeId == null) return null;
return this.themes.find(x => x.id === this.selectedThemeId);
},
selectedThemeCode() {
if (this.selectedTheme == null) return null;
return JSON5.stringify(this.selectedTheme, null, '\t');
},
},
mounted() {
this.$emit('info', this.INFO);
},
methods: {
copyThemeCode() {
copyToClipboard(this.selectedThemeCode);
os.success();
},
uninstall() {
const theme = this.selectedTheme;
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
this.$store.commit('device/set', {
key: 'themes', value: themes
});
os.success();
},
}
});
</script>

View file

@ -1,7 +1,26 @@
<template>
<div class="">
<div class="rfqxtzch _card _vMargin">
<div class="_content">
<FormBase>
<FormSelect v-model:value="lightTheme" v-if="!darkMode">
<template #label>{{ $t('themeForLightMode') }}</template>
<optgroup :label="$t('lightThemes')">
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$t('darkThemes')">
<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
<FormSelect v-model:value="darkTheme" v-else>
<template #label>{{ $t('themeForDarkMode') }}</template>
<optgroup :label="$t('darkThemes')">
<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$t('lightThemes')">
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</FormSelect>
<FormGroup>
<div class="rfqxtzch _formItem _formPanel">
<div class="darkMode" :class="{ disabled: syncDeviceDarkMode }">
<div class="toggleWrapper">
<input type="checkbox" class="dn" id="dn" v-model="darkMode" :disabled="syncDeviceDarkMode"/>
@ -23,85 +42,47 @@
</div>
</div>
</div>
<div class="_content">
<MkSwitch v-model:value="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</MkSwitch>
</div>
</div>
<div class="_card _vMargin">
<div class="_content">
<MkSelect v-model:value="lightTheme">
<template #label>{{ $t('themeForLightMode') }}</template>
<optgroup :label="$t('lightThemes')">
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$t('darkThemes')">
<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</MkSelect>
<MkSelect v-model:value="darkTheme">
<template #label>{{ $t('themeForDarkMode') }}</template>
<optgroup :label="$t('darkThemes')">
<option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
<optgroup :label="$t('lightThemes')">
<option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</optgroup>
</MkSelect>
<a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank" class="_link">{{ $t('_theme.explore') }}</a><MkA to="/theme-editor" class="_link">{{ $t('_theme.make') }}</MkA>
</div>
<div class="_content">
<MkButton primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</MkButton>
<MkButton primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</MkButton>
</div>
</div>
<div class="_card _vMargin">
<div class="_title"><Fa :icon="faDownload"/> {{ $t('_theme.install') }}</div>
<div class="_content">
<MkTextarea v-model:value="installThemeCode">
<span>{{ $t('_theme.code') }}</span>
</MkTextarea>
<MkButton @click="() => install(installThemeCode)" :disabled="installThemeCode == null" primary inline><Fa :icon="faCheck"/> {{ $t('install') }}</MkButton>
<MkButton @click="() => preview(installThemeCode)" :disabled="installThemeCode == null" inline><Fa :icon="faEye"/> {{ $t('preview') }}</MkButton>
</div>
</div>
<div class="_card _vMargin">
<div class="_title"><Fa :icon="faFolderOpen"/> {{ $t('_theme.manage') }}</div>
<div class="_content">
<MkSelect v-model:value="selectedThemeId">
<option v-for="x in installedThemes" :value="x.id" :key="x.id">{{ x.name }}</option>
</MkSelect>
<template v-if="selectedTheme">
<MkTextarea readonly tall :value="selectedThemeCode">
<span>{{ $t('_theme.code') }}</span>
<template #desc><button @click="copyThemeCode()" class="_textButton">{{ $t('copy') }}</button></template>
</MkTextarea>
<MkButton @click="uninstall()" v-if="!builtinThemes.some(t => t.id == selectedTheme.id)"><Fa :icon="faTrashAlt"/> {{ $t('uninstall') }}</MkButton>
</template>
</div>
</div>
</div>
<FormSwitch v-model:value="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</FormSwitch>
</FormGroup>
<FormButton primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</FormButton>
<FormButton primary v-else @click="wallpaper = null">{{ $t('removeWallpaper') }}</FormButton>
<FormGroup>
<FormLink to="https://assets.msky.cafe/theme/list" external>{{ $t('_theme.explore') }}</FormLink>
<FormLink to="/theme-editor">{{ $t('_theme.make') }}</FormLink>
</FormGroup>
<FormLink to="/settings/theme/install"><template #icon><Fa :icon="faDownload"/></template>{{ $t('_theme.install') }}</FormLink>
<FormLink to="/settings/theme/manage"><template #icon><Fa :icon="faFolderOpen"/></template>{{ $t('_theme.manage') }}</FormLink>
</FormBase>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye } from '@fortawesome/free-solid-svg-icons';
import * as JSON5 from 'json5';
import MkButton from '@/components/ui/button.vue';
import MkSelect from '@/components/ui/select.vue';
import MkSwitch from '@/components/ui/switch.vue';
import MkTextarea from '@/components/ui/textarea.vue';
import { Theme, builtinThemes, applyTheme, validateTheme } from '@/scripts/theme';
import FormSwitch from '@/components/form/switch.vue';
import FormSelect from '@/components/form/select.vue';
import FormRadios from '@/components/form/radios.vue';
import FormBase from '@/components/form/base.vue';
import FormGroup from '@/components/form/group.vue';
import FormLink from '@/components/form/link.vue';
import FormButton from '@/components/form/button.vue';
import { Theme, builtinThemes, applyTheme } from '@/scripts/theme';
import { selectFile } from '@/scripts/select-file';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
MkSelect,
MkSwitch,
MkTextarea,
FormSwitch,
FormSelect,
FormRadios,
FormBase,
FormGroup,
FormLink,
FormButton,
},
emits: ['info'],
@ -113,8 +94,6 @@ export default defineComponent({
icon: faPalette
},
builtinThemes,
installThemeCode: null,
selectedThemeId: null,
wallpaper: localStorage.getItem('wallpaper'),
faPalette, faDownload, faFolderOpen, faCheck, faTrashAlt, faEye
}
@ -156,16 +135,6 @@ export default defineComponent({
get() { return this.$store.state.device.syncDeviceDarkMode; },
set(value) { this.$store.commit('device/set', { key: 'syncDeviceDarkMode', value }); }
},
selectedTheme() {
if (this.selectedThemeId == null) return null;
return this.themes.find(x => x.id === this.selectedThemeId);
},
selectedThemeCode() {
if (this.selectedTheme == null) return null;
return JSON5.stringify(this.selectedTheme, null, '\t');
},
},
watch: {
@ -207,292 +176,230 @@ export default defineComponent({
this.wallpaper = file.url;
});
},
copyThemeCode() {
copyToClipboard(this.selectedThemeCode);
os.success();
},
parseThemeCode(code) {
let theme;
try {
theme = JSON5.parse(code);
} catch (e) {
os.dialog({
type: 'error',
text: this.$t('_theme.invalid')
});
return false;
}
if (!validateTheme(theme)) {
os.dialog({
type: 'error',
text: this.$t('_theme.invalid')
});
return false;
}
if (this.$store.state.device.themes.some(t => t.id === theme.id)) {
os.dialog({
type: 'info',
text: this.$t('_theme.alreadyInstalled')
});
return false;
}
return theme;
},
preview(code) {
const theme = this.parseThemeCode(code);
if (theme) applyTheme(theme, false);
},
install(code) {
const theme = this.parseThemeCode(code);
if (!theme) return;
const themes = this.$store.state.device.themes.concat(theme);
this.$store.commit('device/set', {
key: 'themes', value: themes
});
os.dialog({
type: 'success',
text: this.$t('_theme.installed', { name: theme.name })
});
},
uninstall() {
const theme = this.selectedTheme;
const themes = this.$store.state.device.themes.filter(t => t.id != theme.id);
this.$store.commit('device/set', {
key: 'themes', value: themes
});
os.success();
},
}
});
</script>
<style lang="scss" scoped>
.rfqxtzch {
> ._content {
> .darkMode {
position: relative;
padding: 32px 0;
padding: 16px;
&.disabled {
opacity: 0.7;
> .darkMode {
position: relative;
padding: 32px 0;
&, * {
cursor: not-allowed !important;
}
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
}
.toggleWrapper {
.toggleWrapper {
position: absolute;
top: 50%;
left: 50%;
overflow: hidden;
padding: 0 100px;
transform: translate3d(-50%, -50%, 0);
input {
position: absolute;
top: 50%;
left: 50%;
overflow: hidden;
padding: 0 100px;
transform: translate3d(-50%, -50%, 0);
left: -99em;
}
}
input {
position: absolute;
left: -99em;
}
.toggle {
cursor: pointer;
display: inline-block;
position: relative;
width: 90px;
height: 50px;
background-color: #83D8FF;
border-radius: 90px - 6;
transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
> .before, > .after {
position: absolute;
top: 15px;
font-size: 18px;
transition: color 1s ease;
}
.toggle {
cursor: pointer;
display: inline-block;
position: relative;
width: 90px;
height: 50px;
background-color: #83D8FF;
border-radius: 90px - 6;
transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
> .before {
left: -70px;
color: var(--accent);
}
> .before, > .after {
position: absolute;
top: 15px;
font-size: 18px;
transition: color 1s ease;
}
> .after {
right: -68px;
color: var(--fg);
}
}
.toggle__handler {
display: inline-block;
position: relative;
z-index: 1;
top: 3px;
left: 3px;
width: 50px - 6;
height: 50px - 6;
background-color: #FFCF96;
border-radius: 50px;
box-shadow: 0 2px 6px rgba(0,0,0,.3);
transition: all 400ms cubic-bezier(0.68, -0.55, 0.265, 1.55) !important;
transform: rotate(-45deg);
.crater {
position: absolute;
background-color: #E8CDA5;
opacity: 0;
transition: opacity 200ms ease-in-out !important;
border-radius: 100%;
}
.crater--1 {
top: 18px;
left: 10px;
width: 4px;
height: 4px;
}
.crater--2 {
top: 28px;
left: 22px;
width: 6px;
height: 6px;
}
.crater--3 {
top: 10px;
left: 25px;
width: 8px;
height: 8px;
}
}
.star {
position: absolute;
background-color: #ffffff;
transition: all 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
border-radius: 50%;
}
.star--1 {
top: 10px;
left: 35px;
z-index: 0;
width: 30px;
height: 3px;
}
.star--2 {
top: 18px;
left: 28px;
z-index: 1;
width: 30px;
height: 3px;
}
.star--3 {
top: 27px;
left: 40px;
z-index: 0;
width: 30px;
height: 3px;
}
.star--4,
.star--5,
.star--6 {
opacity: 0;
transition: all 300ms 0 cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--4 {
top: 16px;
left: 11px;
z-index: 0;
width: 2px;
height: 2px;
transform: translate3d(3px,0,0);
}
.star--5 {
top: 32px;
left: 17px;
z-index: 0;
width: 3px;
height: 3px;
transform: translate3d(3px,0,0);
}
.star--6 {
top: 36px;
left: 28px;
z-index: 0;
width: 2px;
height: 2px;
transform: translate3d(3px,0,0);
}
input:checked {
+ .toggle {
background-color: #749DD6;
> .before {
left: -70px;
color: var(--accent);
color: var(--fg);
}
> .after {
right: -68px;
color: var(--fg);
}
}
.toggle__handler {
display: inline-block;
position: relative;
z-index: 1;
top: 3px;
left: 3px;
width: 50px - 6;
height: 50px - 6;
background-color: #FFCF96;
border-radius: 50px;
box-shadow: 0 2px 6px rgba(0,0,0,.3);
transition: all 400ms cubic-bezier(0.68, -0.55, 0.265, 1.55) !important;
transform: rotate(-45deg);
.crater {
position: absolute;
background-color: #E8CDA5;
opacity: 0;
transition: opacity 200ms ease-in-out !important;
border-radius: 100%;
color: var(--accent);
}
.crater--1 {
top: 18px;
left: 10px;
.toggle__handler {
background-color: #FFE5B5;
transform: translate3d(40px, 0, 0) rotate(0);
.crater { opacity: 1; }
}
.star--1 {
width: 2px;
height: 2px;
}
.star--2 {
width: 4px;
height: 4px;
transform: translate3d(-5px, 0, 0);
}
.crater--2 {
top: 28px;
left: 22px;
width: 6px;
height: 6px;
.star--3 {
width: 2px;
height: 2px;
transform: translate3d(-7px, 0, 0);
}
.crater--3 {
top: 10px;
left: 25px;
width: 8px;
height: 8px;
.star--4,
.star--5,
.star--6 {
opacity: 1;
transform: translate3d(0,0,0);
}
}
.star {
position: absolute;
background-color: #ffffff;
transition: all 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
border-radius: 50%;
}
.star--4 {
transition: all 300ms 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--1 {
top: 10px;
left: 35px;
z-index: 0;
width: 30px;
height: 3px;
}
.star--5 {
transition: all 300ms 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--2 {
top: 18px;
left: 28px;
z-index: 1;
width: 30px;
height: 3px;
}
.star--3 {
top: 27px;
left: 40px;
z-index: 0;
width: 30px;
height: 3px;
}
.star--4,
.star--5,
.star--6 {
opacity: 0;
transition: all 300ms 0 cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--4 {
top: 16px;
left: 11px;
z-index: 0;
width: 2px;
height: 2px;
transform: translate3d(3px,0,0);
}
.star--5 {
top: 32px;
left: 17px;
z-index: 0;
width: 3px;
height: 3px;
transform: translate3d(3px,0,0);
}
.star--6 {
top: 36px;
left: 28px;
z-index: 0;
width: 2px;
height: 2px;
transform: translate3d(3px,0,0);
}
input:checked {
+ .toggle {
background-color: #749DD6;
> .before {
color: var(--fg);
}
> .after {
color: var(--accent);
}
.toggle__handler {
background-color: #FFE5B5;
transform: translate3d(40px, 0, 0) rotate(0);
.crater { opacity: 1; }
}
.star--1 {
width: 2px;
height: 2px;
}
.star--2 {
width: 4px;
height: 4px;
transform: translate3d(-5px, 0, 0);
}
.star--3 {
width: 2px;
height: 2px;
transform: translate3d(-7px, 0, 0);
}
.star--4,
.star--5,
.star--6 {
opacity: 1;
transform: translate3d(0,0,0);
}
.star--4 {
transition: all 300ms 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--5 {
transition: all 300ms 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--6 {
transition: all 300ms 400ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
.star--6 {
transition: all 300ms 400ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;
}
}
}

View file

@ -1,47 +1,53 @@
<template>
<div class="_section">
<div class="_card">
<MkTab v-model:value="tab">
<option value="soft">{{ $t('_wordMute.soft') }}</option>
<option value="hard">{{ $t('_wordMute.hard') }}</option>
</MkTab>
<div class="_content">
<div>
<MkTab v-model:value="tab">
<option value="soft">{{ $t('_wordMute.soft') }}</option>
<option value="hard">{{ $t('_wordMute.hard') }}</option>
</MkTab>
<FormBase>
<div class="_formItem">
<div v-show="tab === 'soft'">
<MkInfo>{{ $t('_wordMute.softDescription') }}</MkInfo>
<MkTextarea v-model:value="softMutedWords">
<FormTextarea v-model:value="softMutedWords">
<span>{{ $t('_wordMute.muteWords') }}</span>
<template #desc>{{ $t('_wordMute.muteWordsDescription') }}<br>{{ $t('_wordMute.muteWordsDescription2') }}</template>
</MkTextarea>
</FormTextarea>
</div>
<div v-show="tab === 'hard'">
<MkInfo>{{ $t('_wordMute.hardDescription') }}</MkInfo>
<MkTextarea v-model:value="hardMutedWords" style="margin-bottom: 16px;">
<FormTextarea v-model:value="hardMutedWords">
<span>{{ $t('_wordMute.muteWords') }}</span>
<template #desc>{{ $t('_wordMute.muteWordsDescription') }}<br>{{ $t('_wordMute.muteWordsDescription2') }}</template>
</MkTextarea>
<div v-if="hardWordMutedNotesCount != null" class="_caption">{{ $t('_wordMute.mutedNotes') }}: {{ hardWordMutedNotesCount | number }}</div>
</FormTextarea>
<FormKeyValueView v-if="hardWordMutedNotesCount != null">
<template #key>{{ $t('_wordMute.mutedNotes') }}</template>
<template #value>{{ number(hardWordMutedNotesCount) }}</template>
</FormKeyValueView>
</div>
</div>
<div class="_footer">
<MkButton @click="save()" primary inline :disabled="!changed"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
</div>
</div>
<FormButton @click="save()" primary inline :disabled="!changed"><Fa :icon="faSave"/> {{ $t('save') }}</FormButton>
</FormBase>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faCommentSlash, faSave } from '@fortawesome/free-solid-svg-icons';
import MkButton from '@/components/ui/button.vue';
import MkTextarea from '@/components/ui/textarea.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormBase from '@/components/form/base.vue';
import FormKeyValueView from '@/components/form/key-value-view.vue';
import FormButton from '@/components/form/button.vue';
import MkTab from '@/components/tab.vue';
import MkInfo from '@/components/ui/info.vue';
import * as os from '@/os';
import number from '@/filters/number';
export default defineComponent({
components: {
MkButton,
MkTextarea,
FormBase,
FormButton,
FormTextarea,
FormKeyValueView,
MkTab,
MkInfo,
},
@ -97,6 +103,8 @@ export default defineComponent({
});
this.changed = false;
},
number
}
});
</script>

View file

@ -1,5 +1,5 @@
<template>
<div class="_section">
<div>
<MkPagination :pagination="pagination" #default="{items}" class="mk-following-or-followers _content" ref="list">
<div class="users">
<MkUserInfo class="user" v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :user="user" :key="user.id"/>

View file

@ -1,15 +1,24 @@
<template>
<div>
<div ref="chart"></div>
</div>
<MkContainer>
<template #header><Fa :icon="faChartBar" style="margin-right: 0.5em;"/>{{ $t('activity') }}</template>
<div style="padding: 8px;">
<div ref="chart"></div>
</div>
</MkContainer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ApexCharts from 'apexcharts';
import { faChartBar } from '@fortawesome/free-solid-svg-icons';
import * as os from '@/os';
import MkContainer from '@/components/ui/container.vue';
export default defineComponent({
components: {
MkContainer,
},
props: {
user: {
type: Object,
@ -25,7 +34,8 @@ export default defineComponent({
return {
fetching: true,
data: [],
peak: null
peak: null,
faChartBar,
};
},
mounted() {

View file

@ -1,29 +1,43 @@
<template>
<div class="ujigsodd">
<MkLoading v-if="fetching"/>
<div class="stream" v-if="!fetching && images.length > 0">
<MkA v-for="image in images"
class="img"
:style="`background-image: url(${thumbnail(image.file)})`"
:to="notePage(image.note)"
></MkA>
<MkContainer>
<template #header><Fa :icon="faImage" style="margin-right: 0.5em;"/>{{ $t('images') }}</template>
<div class="ujigsodd">
<MkLoading v-if="fetching"/>
<div class="stream" v-if="!fetching && images.length > 0">
<MkA v-for="image in images"
class="img"
:style="`background-image: url(${thumbnail(image.file)})`"
:to="notePage(image.note)"
></MkA>
</div>
<p class="empty" v-if="!fetching && images.length == 0">{{ $t('nothing') }}</p>
</div>
<p class="empty" v-if="!fetching && images.length == 0">{{ $t('nothing') }}</p>
</div>
</MkContainer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { faImage } from '@fortawesome/free-solid-svg-icons';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import notePage from '../../filters/note';
import * as os from '@/os';
import MkContainer from '@/components/ui/container.vue';
export default defineComponent({
props: ['user'],
components: {
MkContainer,
},
props: {
user: {
type: Object,
required: true
},
},
data() {
return {
fetching: true,
images: []
images: [],
faImage
};
},
mounted() {
@ -37,7 +51,7 @@ export default defineComponent({
os.api('users/notes', {
userId: this.user.id,
fileType: image,
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
excludeNsfw: this.$store.state.device.nsfw !== 'ignore',
limit: 9,
}).then(notes => {
for (const note of notes) {
@ -66,6 +80,8 @@ export default defineComponent({
<style lang="scss" scoped>
.ujigsodd {
padding: 8px;
> .stream {
display: flex;
justify-content: center;

View file

@ -1,115 +1,113 @@
<template>
<div class="mk-user-page" v-if="user" v-size="{ max: [500] }">
<!-- TODO -->
<!-- <div class="punished" v-if="user.isSuspended"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSuspended') }}</div> -->
<!-- <div class="punished" v-if="user.isSilenced"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSilenced') }}</div> -->
<div>
<div class="mk-user-page" v-if="user" v-size="{ max: [500] }" :class="{ _section: narrow === false }">
<!-- TODO -->
<!-- <div class="punished" v-if="user.isSuspended"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSuspended') }}</div> -->
<!-- <div class="punished" v-if="user.isSilenced"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $t('userSilenced') }}</div> -->
<div class="profile _section _fitBottom">
<MkRemoteCaution v-if="user.host != null" :href="user.url" class="_content _vMargin"/>
<div class="main">
<div class="profile _vMargin" :class="{ _section: narrow === true }">
<MkRemoteCaution v-if="user.host != null" :href="user.url" class="_content _vMargin"/>
<div class="_content _vMargin" :key="user.id">
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
<div class="fade"></div>
<div class="title">
<MkUserName class="name" :user="user" :nowrap="true"/>
<div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><Fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><Fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><Fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><Fa :icon="faRobot"/></span>
<div class="_content _panel _vMargin" :key="user.id">
<div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style"></div>
<div class="fade"></div>
<div class="title">
<MkUserName class="name" :user="user" :nowrap="true"/>
<div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><Fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><Fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><Fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><Fa :icon="faRobot"/></span>
</div>
</div>
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
<div class="actions" v-if="$store.getters.isSignedIn">
<button @click="menu" class="menu _button"><Fa :icon="faEllipsisH"/></button>
<MkFollowButton v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
</div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<div class="title">
<MkUserName :user="user" :nowrap="false" class="name"/>
<div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><Fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><Fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><Fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><Fa :icon="faRobot"/></span>
</div>
</div>
<div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
</div>
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><Fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><Fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><Fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
</dl>
</div>
<div class="fields" v-if="user.fields.length > 0">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name">
<Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
</dt>
<dd class="value">
<Mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<div class="status">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }">
<b>{{ number(user.notesCount) }}</b>
<span>{{ $t('notes') }}</span>
</MkA>
<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
<b>{{ number(user.followingCount) }}</b>
<span>{{ $t('following') }}</span>
</MkA>
<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
<b>{{ number(user.followersCount) }}</b>
<span>{{ $t('followers') }}</span>
</MkA>
</div>
</div>
<span class="followed" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id && user.isFollowed">{{ $t('followsYou') }}</span>
<div class="actions" v-if="$store.getters.isSignedIn">
<button @click="menu" class="menu _button"><Fa :icon="faEllipsisH"/></button>
<MkFollowButton v-if="$store.state.i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
</div>
<template v-if="page === 'index'">
<div v-if="user.pinnedNotes.length > 0" :class="{ _section: narrow === true, _vMargin: narrow === false }">
<XNote v-for="note in user.pinnedNotes" class="note _content _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/>
</div>
</div>
<MkAvatar class="avatar" :user="user" :disable-preview="true"/>
<div class="title">
<MkUserName :user="user" :nowrap="false" class="name"/>
<div class="bottom">
<span class="username"><MkAcct :user="user" :detail="true" /></span>
<span v-if="user.isAdmin" :title="$t('isAdmin')" style="color: var(--badge);"><Fa :icon="faBookmark"/></span>
<span v-if="!user.isAdmin && user.isModerator" :title="$t('isModerator')" style="color: var(--badge);"><Fa :icon="farBookmark"/></span>
<span v-if="user.isLocked" :title="$t('isLocked')"><Fa :icon="faLock"/></span>
<span v-if="user.isBot" :title="$t('isBot')"><Fa :icon="faRobot"/></span>
<div v-if="narrow === true" class="_section">
<XPhotos class="_content _vMargin" :user="user" :key="user.id"/>
<XActivity class="_content _vMargin" :user="user" :key="user.id"/>
</div>
</div>
<div class="description">
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/>
<p v-else class="empty">{{ $t('noAccountDescription') }}</p>
</div>
<div class="fields system">
<dl class="field" v-if="user.location">
<dt class="name"><Fa :icon="faMapMarker" fixed-width/> {{ $t('location') }}</dt>
<dd class="value">{{ user.location }}</dd>
</dl>
<dl class="field" v-if="user.birthday">
<dt class="name"><Fa :icon="faBirthdayCake" fixed-width/> {{ $t('birthday') }}</dt>
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ $t('yearsOld', { age }) }})</dd>
</dl>
<dl class="field">
<dt class="name"><Fa :icon="faCalendarAlt" fixed-width/> {{ $t('registeredDate') }}</dt>
<dd class="value">{{ new Date(user.createdAt).toLocaleString() }} (<MkTime :time="user.createdAt"/>)</dd>
</dl>
</div>
<div class="fields" v-if="user.fields.length > 0">
<dl class="field" v-for="(field, i) in user.fields" :key="i">
<dt class="name">
<Mfm :text="field.name" :plain="true" :custom-emojis="user.emojis" :colored="false"/>
</dt>
<dd class="value">
<Mfm :text="field.value" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :colored="false"/>
</dd>
</dl>
</div>
<div class="status">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }">
<b>{{ number(user.notesCount) }}</b>
<span>{{ $t('notes') }}</span>
</MkA>
<MkA :to="userPage(user, 'following')" :class="{ active: page === 'following' }">
<b>{{ number(user.followingCount) }}</b>
<span>{{ $t('following') }}</span>
</MkA>
<MkA :to="userPage(user, 'followers')" :class="{ active: page === 'followers' }">
<b>{{ number(user.followersCount) }}</b>
<span>{{ $t('followers') }}</span>
</MkA>
</div>
<div :class="{ _section: narrow === true, _vMargin: narrow === false }">
<XUserTimeline :user="user" class="_content"/>
</div>
</template>
<XFollowList v-else-if="page === 'following'" :class="{ _section: narrow === true, _vMargin: narrow === false }" type="following" :user="user"/>
<XFollowList v-else-if="page === 'followers'" :class="{ _section: narrow === true, _vMargin: narrow === false }" type="followers" :user="user"/>
</div>
<div class="side" v-if="narrow === false">
<XPhotos class="_vMargin" :user="user" :key="user.id"/>
<XActivity class="_vMargin" :user="user" :key="user.id"/>
</div>
</div>
<template v-if="page === 'index'">
<div class="_section">
<div class="_content _vMargin" v-if="user.pinnedNotes.length > 0">
<XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :detail="true" :pinned="true"/>
</div>
<MkFolder :body-togglable="true" class="_content _vMargin" persist-key="user-images">
<template #header><Fa :icon="faImage" style="margin-right: 0.5em;"/>{{ $t('images') }}</template>
<div>
<XPhotos :user="user" :key="user.id"/>
</div>
</MkFolder>
<MkFolder :body-togglable="true" class="_content _vMargin" persist-key="user-activity">
<template #header><Fa :icon="faChartBar" style="margin-right: 0.5em;"/>{{ $t('activity') }}</template>
<div>
<XActivity :user="user" :key="user.id"/>
</div>
</MkFolder>
</div>
<div class="_section">
<XUserTimeline :user="user" class="_content"/>
</div>
</template>
<XFollowList v-else-if="page === 'following'" type="following" :user="user"/>
<XFollowList v-else-if="page === 'followers'" type="followers" :user="user"/>
</div>
<div v-else-if="error">
<MkError @retry="fetch()"/>
<div v-else-if="error">
<MkError @retry="fetch()"/>
</div>
</div>
</template>
@ -170,6 +168,7 @@ export default defineComponent({
user: null,
error: null,
parallaxAnimationId: null,
narrow: null,
faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt
};
},
@ -197,6 +196,7 @@ export default defineComponent({
mounted() {
window.requestAnimationFrame(this.parallaxLoop);
this.narrow = this.$el.clientWidth < 1000;
},
beforeUnmount() {
@ -254,220 +254,234 @@ export default defineComponent({
<style lang="scss" scoped>
.mk-user-page {
> .punished {
font-size: 0.8em;
padding: 16px;
}
display: flex;
max-width: 1050px;
margin: 0 auto;
> .main {
flex: 1;
> .profile {
> ._content {
position: relative;
overflow: hidden;
> .punished {
font-size: 0.8em;
padding: 16px;
}
> .banner-container {
> .profile {
> ._content {
position: relative;
height: 250px;
overflow: hidden;
background-size: cover;
background-position: center;
border-radius: 12px;
> .banner {
height: 100%;
background-color: #4c5e6d;
> .banner-container {
position: relative;
height: 250px;
overflow: hidden;
background-size: cover;
background-position: center;
box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
will-change: background-position;
}
> .fade {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 78px;
background: linear-gradient(transparent, rgba(#000, 0.7));
}
> .banner {
height: 100%;
background-color: #4c5e6d;
background-size: cover;
background-position: center;
box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
will-change: background-position;
}
> .followed {
position: absolute;
top: 12px;
left: 12px;
padding: 4px 8px;
color: #fff;
background: rgba(0, 0, 0, 0.7);
font-size: 0.7em;
border-radius: 6px;
}
> .fade {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 78px;
background: linear-gradient(transparent, rgba(#000, 0.7));
}
> .actions {
position: absolute;
top: 12px;
right: 12px;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
background: rgba(0, 0, 0, 0.2);
padding: 8px;
border-radius: 24px;
> .menu {
vertical-align: bottom;
height: 31px;
width: 31px;
> .followed {
position: absolute;
top: 12px;
left: 12px;
padding: 4px 8px;
color: #fff;
text-shadow: 0 0 8px #000;
font-size: 16px;
background: rgba(0, 0, 0, 0.7);
font-size: 0.7em;
border-radius: 6px;
}
> .koudoku {
margin-left: 4px;
vertical-align: bottom;
}
}
> .actions {
position: absolute;
top: 12px;
right: 12px;
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
background: rgba(0, 0, 0, 0.2);
padding: 8px;
border-radius: 24px;
> .title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 0 0 8px 154px;
box-sizing: border-box;
color: #fff;
> .menu {
vertical-align: bottom;
height: 31px;
width: 31px;
color: #fff;
text-shadow: 0 0 8px #000;
font-size: 16px;
}
> .name {
display: block;
margin: 0;
line-height: 32px;
font-weight: bold;
font-size: 1.8em;
text-shadow: 0 0 8px #000;
> .koudoku {
margin-left: 4px;
vertical-align: bottom;
}
}
> .bottom {
> * {
display: inline-block;
margin-right: 16px;
line-height: 20px;
opacity: 0.8;
> .title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 0 0 8px 154px;
box-sizing: border-box;
color: #fff;
&.username {
font-weight: bold;
> .name {
display: block;
margin: 0;
line-height: 32px;
font-weight: bold;
font-size: 1.8em;
text-shadow: 0 0 8px #000;
}
> .bottom {
> * {
display: inline-block;
margin-right: 16px;
line-height: 20px;
opacity: 0.8;
&.username {
font-weight: bold;
}
}
}
}
}
}
> .title {
display: none;
text-align: center;
padding: 50px 8px 16px 8px;
font-weight: bold;
border-bottom: solid 1px var(--divider);
> .bottom {
> * {
display: inline-block;
margin-right: 8px;
opacity: 0.8;
}
}
}
> .avatar {
display: block;
position: absolute;
top: 170px;
left: 16px;
z-index: 2;
width: 120px;
height: 120px;
box-shadow: 1px 1px 3px rgba(#000, 0.2);
}
> .description {
padding: 24px 24px 24px 154px;
font-size: 0.95em;
> .empty {
margin: 0;
opacity: 0.5;
}
}
> .fields {
padding: 24px;
font-size: 0.9em;
border-top: solid 1px var(--divider);
> .field {
display: flex;
padding: 0;
margin: 0;
align-items: center;
&:not(:last-child) {
margin-bottom: 8px;
}
> .name {
width: 30%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: bold;
text-align: center;
}
> .value {
width: 70%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&.system > .field > .name {
}
}
> .status {
display: flex;
padding: 24px;
border-top: solid 1px var(--divider);
> a {
flex: 1;
> .title {
display: none;
text-align: center;
padding: 50px 8px 16px 8px;
font-weight: bold;
border-bottom: solid 1px var(--divider);
&.active {
color: var(--accent);
> .bottom {
> * {
display: inline-block;
margin-right: 8px;
opacity: 0.8;
}
}
}
> .avatar {
display: block;
position: absolute;
top: 170px;
left: 16px;
z-index: 2;
width: 120px;
height: 120px;
box-shadow: 1px 1px 3px rgba(#000, 0.2);
}
> .description {
padding: 24px 24px 24px 154px;
font-size: 0.95em;
> .empty {
margin: 0;
opacity: 0.5;
}
}
> .fields {
padding: 24px;
font-size: 0.9em;
border-top: solid 1px var(--divider);
> .field {
display: flex;
padding: 0;
margin: 0;
align-items: center;
&:not(:last-child) {
margin-bottom: 8px;
}
> .name {
width: 30%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: bold;
text-align: center;
}
> .value {
width: 70%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&:hover {
text-decoration: none;
&.system > .field > .name {
}
}
> b {
display: block;
line-height: 16px;
}
> .status {
display: flex;
padding: 24px;
border-top: solid 1px var(--divider);
> span {
font-size: 70%;
> a {
flex: 1;
text-align: center;
&.active {
color: var(--accent);
}
&:hover {
text-decoration: none;
}
> b {
display: block;
line-height: 16px;
}
> span {
font-size: 70%;
}
}
}
}
}
> .content {
margin-bottom: var(--margin);
}
}
> .content {
margin-bottom: var(--margin);
> .side {
flex-basis: 300px;
margin-left: var(--margin);
}
&.max-width_500px {
> .profile > ._content {
display: block;
> .main > .profile > ._content {
> .banner-container {
height: 140px;

View file

@ -1,11 +1,5 @@
<template>
<div class="rsqzvsbo _section" v-if="meta">
<div class="about">
<h1>{{ instanceName }}</h1>
<div class="desc" v-html="meta.description || $t('introMisskey')"></div>
<MkButton @click="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</MkButton>
<MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton>
</div>
<div class="blocks">
<XBlock class="block" v-for="path in meta.pinnedPages" :initial-path="path" :key="path"/>
</div>
@ -68,28 +62,6 @@ export default defineComponent({
.rsqzvsbo {
text-align: center;
> .about {
display: inline-block;
padding: 24px;
margin-bottom: var(--margin);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
background: rgba(0, 0, 0, 0.5);
border-radius: var(--radius);
text-align: center;
box-sizing: border-box;
min-width: 300px;
max-width: 800px;
&, * {
color: #fff !important;
}
> h1 {
margin: 0 0 16px 0;
}
}
> .blocks {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));