diff --git a/src/client/app/common/define-widget.ts b/src/client/app/common/define-widget.ts index 7b98c0903..0b2bc3656 100644 --- a/src/client/app/common/define-widget.ts +++ b/src/client/app/common/define-widget.ts @@ -18,61 +18,65 @@ export default function<T extends object>(data: { default: false } }, + computed: { id(): string { return this.widget.id; + }, + + props(): T { + return this.widget.data; } }, + data() { return { - props: data.props ? data.props() : {} as T, - bakedOldProps: null, - preventSave: false + bakedOldProps: null }; }, + created() { - if (this.props) { - Object.keys(this.props).forEach(prop => { - if (this.widget.data.hasOwnProperty(prop)) { - this.props[prop] = this.widget.data[prop]; - } - }); - } + this.mergeProps(); + + this.$watch('props', () => { + this.mergeProps(); + }); this.bakeProps(); + }, - this.$watch('props', newProps => { - if (this.preventSave) { - this.preventSave = false; - this.bakeProps(); - return; + methods: { + bakeProps() { + this.bakedOldProps = JSON.stringify(this.props); + }, + + mergeProps() { + if (data.props) { + const defaultProps = data.props(); + Object.keys(defaultProps).forEach(prop => { + if (!this.props.hasOwnProperty(prop)) { + Vue.set(this.props, prop, defaultProps[prop]); + } + }); } - if (this.bakedOldProps == JSON.stringify(newProps)) return; + }, + + save() { + if (this.bakedOldProps == JSON.stringify(this.props)) return; this.bakeProps(); if (this.isMobile) { (this as any).api('i/update_mobile_home', { id: this.id, - data: newProps - }).then(() => { - (this as any).os.i.clientSettings.mobileHome.find(w => w.id == this.id).data = newProps; + data: this.props }); } else { (this as any).api('i/update_home', { id: this.id, - data: newProps - }).then(() => { - (this as any).os.i.clientSettings.home.find(w => w.id == this.id).data = newProps; + data: this.props }); } - }, { - deep: true - }); - }, - methods: { - bakeProps() { - this.bakedOldProps = JSON.stringify(this.props); } } }); diff --git a/src/client/app/common/mios.ts b/src/client/app/common/mios.ts index 4e471cf96..7dcae4794 100644 --- a/src/client/app/common/mios.ts +++ b/src/client/app/common/mios.ts @@ -3,6 +3,7 @@ import { EventEmitter } from 'eventemitter3'; import * as merge from 'object-assign-deep'; import * as uuid from 'uuid'; +import initStore from '../store'; import { hostname, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config'; import Progress from './scripts/loading'; import Connection from './scripts/streaming/stream'; @@ -16,16 +17,6 @@ import Err from '../common/views/components/connect-failed.vue'; import { LocalTimelineStreamManager } from './scripts/streaming/local-timeline'; import { GlobalTimelineStreamManager } from './scripts/streaming/global-timeline'; -const defaultSettings = { - fetchOnScroll: true, - showMaps: true, - showPostFormOnTopOfTl: false, - gradientWindowHeader: false, - showReplyTarget: true, - showMyRenotes: true, - showRenotedMyNotes: true -}; - //#region api requests let spinner = null; let pending = 0; @@ -117,6 +108,8 @@ export default class MiOS extends EventEmitter { return localStorage.getItem('enableSounds') == 'true'; } + public store: ReturnType<typeof initStore>; + public apis: API; /** @@ -232,6 +225,11 @@ export default class MiOS extends EventEmitter { console.error.apply(null, args); } + public bakeMe() { + // ローカルストレージにキャッシュ + localStorage.setItem('me', JSON.stringify(this.i)); + } + public signout() { localStorage.removeItem('me'); document.cookie = `i=; domain=${hostname}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; @@ -243,6 +241,8 @@ export default class MiOS extends EventEmitter { * @param callback A function that call when initialized */ public async init(callback) { + this.store = initStore(this); + //#region Init stream managers this.streams.serverStream = new ServerStreamManager(this); @@ -307,16 +307,11 @@ export default class MiOS extends EventEmitter { // フェッチが完了したとき const fetched = me => { - if (me) { - // デフォルトの設定をマージ - me.clientSettings = Object.assign(defaultSettings, me.clientSettings); - - // ローカルストレージにキャッシュ - localStorage.setItem('me', JSON.stringify(me)); - } - this.i = me; + // ローカルストレージにキャッシュ + this.bakeMe(); + this.emit('signedin'); // Finish init @@ -333,6 +328,14 @@ export default class MiOS extends EventEmitter { // Get cached account data const cachedMe = JSON.parse(localStorage.getItem('me')); + //#region キャッシュされた設定を復元 + const cachedSettings = JSON.parse(localStorage.getItem('settings')); + + if (cachedSettings) { + this.store.commit('settings/init', cachedSettings); + } + //#endregion + // キャッシュがあったとき if (cachedMe) { if (cachedMe.token == null) { @@ -346,12 +349,25 @@ export default class MiOS extends EventEmitter { // 後から新鮮なデータをフェッチ fetchme(cachedMe.token, freshData => { merge(cachedMe, freshData); + + this.store.commit('settings/init', freshData.clientSettings); }); } else { // Get token from cookie const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1]; - fetchme(i, fetched); + fetchme(i, me => { + if (me) { + Object.entries(me.clientSettings).forEach(([key, value]) => { + this.store.commit('settings/set', { key, value }); + }); + + fetched(me); + } else { + // Finish init + callback(); + } + }); } } @@ -456,7 +472,7 @@ export default class MiOS extends EventEmitter { }; const promise = new Promise((resolve, reject) => { - const viaStream = this.stream.hasConnection && + const viaStream = this.stream && this.stream.hasConnection && (localStorage.getItem('apiViaStream') ? localStorage.getItem('apiViaStream') == 'true' : true); if (viaStream) { diff --git a/src/client/app/common/scripts/streaming/home.ts b/src/client/app/common/scripts/streaming/home.ts index 73f2c5302..ddb0d4820 100644 --- a/src/client/app/common/scripts/streaming/home.ts +++ b/src/client/app/common/scripts/streaming/home.ts @@ -25,10 +25,31 @@ export class HomeStream extends Stream { console.log('I updated:', i); } merge(me, i); + + // キャッシュ更新 + os.bakeMe(); + }); + + this.on('clientSettingUpdated', x => { + os.store.commit('settings/set', { + key: x.key, + value: x.value + }); + }); + + this.on('home_updated', x => { + if (x.home) { + os.store.commit('settings/setHome', x.home); + } else { + os.store.commit('settings/setHomeWidget', { + id: x.id, + data: x.data + }); + } }); // トークンが再生成されたとき - // このままではAPIが利用できないので強制的にサインアウトさせる + // このままではMisskeyが利用できないので強制的にサインアウトさせる this.on('my_token_regenerated', () => { alert('%i18n:!common.my-token-regenerated%'); os.signout(); diff --git a/src/client/app/common/views/components/avatar.vue b/src/client/app/common/views/components/avatar.vue new file mode 100644 index 000000000..5aac9c8ba --- /dev/null +++ b/src/client/app/common/views/components/avatar.vue @@ -0,0 +1,38 @@ +<template> + <router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="{ borderRadius: clientSettings.circleIcons ? '100%' : null }"> + <img v-if="disablePreview" :src="`${user.avatarUrl}?thumbnail&size=128`" alt=""/> + <img v-else :src="`${user.avatarUrl}?thumbnail&size=128`" alt="" v-user-preview="user.id"/> + </router-link> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: { + user: { + required: true + }, + target: { + required: false, + default: null + }, + disablePreview: { + required: false, + default: false + } + } +}); +</script> + +<style lang="stylus" scoped> +.mk-avatar + display block + + > img + display inline-block + width 100% + height 100% + margin 0 + border-radius inherit + vertical-align bottom +</style> diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 6bfe43a80..69fed00c7 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -3,6 +3,7 @@ import Vue from 'vue'; import signin from './signin.vue'; import signup from './signup.vue'; import forkit from './forkit.vue'; +import avatar from './avatar.vue'; import nav from './nav.vue'; import noteHtml from './note-html'; import poll from './poll.vue'; @@ -28,6 +29,7 @@ import welcomeTimeline from './welcome-timeline.vue'; Vue.component('mk-signin', signin); Vue.component('mk-signup', signup); Vue.component('mk-forkit', forkit); +Vue.component('mk-avatar', avatar); Vue.component('mk-nav', nav); Vue.component('mk-note-html', noteHtml); Vue.component('mk-poll', poll); diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue index 70df899f5..ba0ab3209 100644 --- a/src/client/app/common/views/components/messaging-room.message.vue +++ b/src/client/app/common/views/components/messaging-room.message.vue @@ -1,8 +1,6 @@ <template> <div class="message" :data-is-me="isMe"> - <router-link class="avatar-anchor" :to="message.user | userPage" :title="message.user | acct" target="_blank"> - <img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/> - </router-link> + <mk-avatar class="avatar" :user="message.user" target="_blank"/> <div class="content"> <div class="balloon" :data-no-text="message.text == null"> <p class="read" v-if="isMe && message.isRead">%i18n:@is-read%</p> @@ -67,20 +65,14 @@ export default Vue.extend({ padding 10px 12px 10px 12px background-color transparent - > .avatar-anchor + > .avatar display block position absolute top 10px - - > .avatar - display block - min-width 54px - min-height 54px - max-width 54px - max-height 54px - margin 0 - border-radius 8px - transition all 0.1s ease + width 54px + height 54px + border-radius 8px + transition all 0.1s ease > .content @@ -201,7 +193,7 @@ export default Vue.extend({ margin-left 4px &:not([data-is-me]) - > .avatar-anchor + > .avatar left 12px > .content @@ -225,7 +217,7 @@ export default Vue.extend({ text-align left &[data-is-me] - > .avatar-anchor + > .avatar right 12px > .content diff --git a/src/client/app/common/views/components/messaging.vue b/src/client/app/common/views/components/messaging.vue index 6f8fcb3a7..11f9c366d 100644 --- a/src/client/app/common/views/components/messaging.vue +++ b/src/client/app/common/views/components/messaging.vue @@ -13,7 +13,7 @@ @click="navigate(user)" tabindex="-1" > - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=32`" alt=""/> + <mk-avatar class="avatar" :user="user"/> <span class="name">{{ user | userName }}</span> <span class="username">@{{ user | acct }}</span> </li> @@ -31,7 +31,7 @@ :key="message.id" > <div> - <img class="avatar" :src="`${isMe(message) ? message.recipient.avatarUrl : message.user.avatarUrl}?thumbnail&size=64`" alt=""/> + <mk-avatar class="avatar" :user="isMe(message) ? message.recipient : message.user"/> <header> <span class="name">{{ isMe(message) ? message.recipient : message.user | userName }}</span> <span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span> diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index 349797690..6fadb030c 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -1,9 +1,7 @@ <template> <div class="mk-welcome-timeline"> <div v-for="note in notes"> - <router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.user.id"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user" target="_blank"/> <div class="body"> <header> <router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link> @@ -69,18 +67,15 @@ export default Vue.extend({ display block clear both - > .avatar-anchor + > .avatar display block float left position -webkit-sticky position sticky top 16px - - > img - display block - width 42px - height 42px - border-radius 6px + width 42px + height 42px + border-radius 6px > .body float right diff --git a/src/client/app/common/views/widgets/access-log.vue b/src/client/app/common/views/widgets/access-log.vue index 0b1c7fe2d..8652e3564 100644 --- a/src/client/app/common/views/widgets/access-log.vue +++ b/src/client/app/common/views/widgets/access-log.vue @@ -61,6 +61,7 @@ export default define({ } else { this.props.design++; } + this.save(); } } }); diff --git a/src/client/app/common/views/widgets/broadcast.vue b/src/client/app/common/views/widgets/broadcast.vue index 96d1d0ef3..75b1d6052 100644 --- a/src/client/app/common/views/widgets/broadcast.vue +++ b/src/client/app/common/views/widgets/broadcast.vue @@ -68,6 +68,7 @@ export default define({ } else { this.props.design++; } + this.save(); } } }); diff --git a/src/client/app/common/views/widgets/calendar.vue b/src/client/app/common/views/widgets/calendar.vue index 0bb503759..41e925378 100644 --- a/src/client/app/common/views/widgets/calendar.vue +++ b/src/client/app/common/views/widgets/calendar.vue @@ -73,6 +73,7 @@ export default define({ } else { this.props.design++; } + this.save(); }, tick() { const now = new Date(); diff --git a/src/client/app/common/views/widgets/photo-stream.vue b/src/client/app/common/views/widgets/photo-stream.vue index c51d932bd..ae5924bb1 100644 --- a/src/client/app/common/views/widgets/photo-stream.vue +++ b/src/client/app/common/views/widgets/photo-stream.vue @@ -59,6 +59,8 @@ export default define({ } else { this.props.design++; } + + this.save(); } } }); diff --git a/src/client/app/common/views/widgets/rss.vue b/src/client/app/common/views/widgets/rss.vue index f0ba11678..b5339add0 100644 --- a/src/client/app/common/views/widgets/rss.vue +++ b/src/client/app/common/views/widgets/rss.vue @@ -40,6 +40,7 @@ export default define({ methods: { func() { this.props.compact = !this.props.compact; + this.save(); }, fetch() { fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, { diff --git a/src/client/app/common/views/widgets/server.vue b/src/client/app/common/views/widgets/server.vue index 2fbc07adf..2fdd60499 100644 --- a/src/client/app/common/views/widgets/server.vue +++ b/src/client/app/common/views/widgets/server.vue @@ -68,6 +68,7 @@ export default define({ } else { this.props.view++; } + this.save(); }, func() { if (this.props.design == 2) { @@ -75,6 +76,7 @@ export default define({ } else { this.props.design++; } + this.save(); } } }); diff --git a/src/client/app/common/views/widgets/slideshow.vue b/src/client/app/common/views/widgets/slideshow.vue index 95be4b94f..459b24a32 100644 --- a/src/client/app/common/views/widgets/slideshow.vue +++ b/src/client/app/common/views/widgets/slideshow.vue @@ -64,6 +64,7 @@ export default define({ } else { this.props.size++; } + this.save(); this.applySize(); }, @@ -111,6 +112,7 @@ export default define({ choose() { (this as any).apis.chooseDriveFolder().then(folder => { this.props.folder = folder ? folder.id : null; + this.save(); this.fetch(); }); } diff --git a/src/client/app/desktop/views/components/friends-maker.vue b/src/client/app/desktop/views/components/friends-maker.vue index af5bde3ad..3c1f8b825 100644 --- a/src/client/app/desktop/views/components/friends-maker.vue +++ b/src/client/app/desktop/views/components/friends-maker.vue @@ -3,9 +3,7 @@ <p class="title">気になるユーザーをフォロー:</p> <div class="users" v-if="!fetching && users.length > 0"> <div class="user" v-for="user in users" :key="user.id"> - <router-link class="avatar-anchor" :to="user | userPage"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="user.id"/> - </router-link> + <mk-avatar class="avatar" :user="user" target="_blank"/> <div class="body"> <router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link> <p class="username">@{{ user | acct }}</p> @@ -86,18 +84,13 @@ export default Vue.extend({ display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 12px 0 0 - - > .avatar - display block - width 42px - height 42px - margin 0 - border-radius 8px - vertical-align bottom + width 42px + height 42px + border-radius 8px > .body float left diff --git a/src/client/app/desktop/views/components/home.vue b/src/client/app/desktop/views/components/home.vue index 4343a7fb7..5337be61f 100644 --- a/src/client/app/desktop/views/components/home.vue +++ b/src/client/app/desktop/views/components/home.vue @@ -53,7 +53,7 @@ <div class="main"> <a @click="hint">カスタマイズのヒント</a> <div> - <mk-post-form v-if="os.i.clientSettings.showPostFormOnTopOfTl"/> + <mk-post-form v-if="clientSettings.showPostFormOnTopOfTl"/> <mk-timeline ref="tl" @loaded="onTlLoaded"/> </div> </div> @@ -63,7 +63,7 @@ <component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/> </div> <div class="main"> - <mk-post-form v-if="os.i.clientSettings.showPostFormOnTopOfTl"/> + <mk-post-form v-if="clientSettings.showPostFormOnTopOfTl"/> <mk-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/> <mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/> </div> @@ -81,6 +81,7 @@ export default Vue.extend({ components: { XDraggable }, + props: { customize: { type: Boolean, @@ -91,61 +92,43 @@ export default Vue.extend({ default: 'timeline' } }, + data() { return { connection: null, connectionId: null, widgetAdderSelected: null, - trash: [], - widgets: { - left: [], - right: [] - } + trash: [] }; }, + computed: { - home: { - get(): any[] { - //#region 互換性のため - (this as any).os.i.clientSettings.home.forEach(w => { - if (w.name == 'rss-reader') w.name = 'rss'; - if (w.name == 'user-recommendation') w.name = 'users'; - if (w.name == 'recommended-polls') w.name = 'polls'; - }); - //#endregion - return (this as any).os.i.clientSettings.home; - }, - set(value) { - (this as any).os.i.clientSettings.home = value; - } + home(): any[] { + return this.$store.state.settings.data.home; }, left(): any[] { return this.home.filter(w => w.place == 'left'); }, right(): any[] { return this.home.filter(w => w.place == 'right'); + }, + widgets(): any { + return { + left: this.left, + right: this.right + }; } }, - created() { - this.widgets.left = this.left; - this.widgets.right = this.right; - this.$watch('os.i.clientSettings', i => { - this.widgets.left = this.left; - this.widgets.right = this.right; - }, { - deep: true - }); - }, + mounted() { this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); - - this.connection.on('home_updated', this.onHomeUpdated); }, + beforeDestroy() { - this.connection.off('home_updated', this.onHomeUpdated); (this as any).os.stream.dispose(this.connectionId); }, + methods: { hint() { (this as any).apis.dialog({ @@ -159,56 +142,44 @@ export default Vue.extend({ }] }); }, + onTlLoaded() { this.$emit('loaded'); }, - onHomeUpdated(data) { - if (data.home) { - (this as any).os.i.clientSettings.home = data.home; - this.widgets.left = data.home.filter(w => w.place == 'left'); - this.widgets.right = data.home.filter(w => w.place == 'right'); - } else { - const w = (this as any).os.i.clientSettings.home.find(w => w.id == data.id); - if (w != null) { - w.data = data.data; - this.$refs[w.id][0].preventSave = true; - this.$refs[w.id][0].props = w.data; - this.widgets.left = (this as any).os.i.clientSettings.home.filter(w => w.place == 'left'); - this.widgets.right = (this as any).os.i.clientSettings.home.filter(w => w.place == 'right'); - } - } - }, + onWidgetContextmenu(widgetId) { const w = (this.$refs[widgetId] as any)[0]; if (w.func) w.func(); }, + onWidgetSort() { this.saveHome(); }, + onTrash(evt) { this.saveHome(); }, + addWidget() { - const widget = { + this.$store.dispatch('settings/addHomeWidget', { name: this.widgetAdderSelected, id: uuid(), place: 'left', data: {} - }; - - this.widgets.left.unshift(widget); - this.saveHome(); + }); }, + saveHome() { const left = this.widgets.left; const right = this.widgets.right; - this.home = left.concat(right); + this.$store.commit('settings/setHome', left.concat(right)); left.forEach(w => w.place = 'left'); right.forEach(w => w.place = 'right'); (this as any).api('i/update_home', { home: this.home }); }, + warp(date) { (this.$refs.tl as any).warp(date); } diff --git a/src/client/app/desktop/views/components/note-detail.sub.vue b/src/client/app/desktop/views/components/note-detail.sub.vue index 5175c8bd4..24550c4e9 100644 --- a/src/client/app/desktop/views/components/note-detail.sub.vue +++ b/src/client/app/desktop/views/components/note-detail.sub.vue @@ -1,8 +1,6 @@ <template> <div class="sub" :title="title"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <header> <div class="left"> @@ -57,18 +55,13 @@ root(isDark) > .main > footer > button color #888 - > .avatar-anchor + > .avatar display block float left margin 0 16px 0 0 - - > .avatar - display block - width 44px - height 44px - margin 0 - border-radius 4px - vertical-align bottom + width 44px + height 44px + border-radius 4px > .main float left diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue index 525023349..5d07dc90d 100644 --- a/src/client/app/desktop/views/components/note-detail.vue +++ b/src/client/app/desktop/views/components/note-detail.vue @@ -18,18 +18,14 @@ </div> <div class="renote" v-if="isRenote"> <p> - <router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> %fa:retweet% <router-link class="name" :href="note.user | userPage">{{ note.user | userName }}</router-link> がRenote </p> </div> <article> - <router-link class="avatar-anchor" :to="p.user | userPage"> - <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> - </router-link> + <mk-avatar class="avatar" :user="p.user"/> <header> <router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> <span class="username">@{{ p.user | acct }}</span> @@ -159,7 +155,7 @@ export default Vue.extend({ // Draw map if (this.p.geo) { - const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true; + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).clientSettings.showMaps : true; if (shouldShowMap) { (this as any).os.getGoogleMaps().then(maps => { const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); @@ -262,17 +258,12 @@ root(isDark) margin 0 padding 16px 32px - .avatar-anchor + .avatar display inline-block - - .avatar - vertical-align bottom - min-width 28px - min-height 28px - max-width 28px - max-height 28px - margin 0 8px 0 0 - border-radius 6px + width 28px + height 28px + margin 0 8px 0 0 + border-radius 6px [data-fa] margin-right 4px @@ -298,18 +289,10 @@ root(isDark) > footer > button color isDark ? #707b97 : #888 - > .avatar-anchor - display block + > .avatar width 60px height 60px - - > .avatar - display block - width 60px - height 60px - margin 0 - border-radius 8px - vertical-align bottom + border-radius 8px > header position absolute diff --git a/src/client/app/desktop/views/components/note-preview.vue b/src/client/app/desktop/views/components/note-preview.vue index b45814e51..43eb15988 100644 --- a/src/client/app/desktop/views/components/note-preview.vue +++ b/src/client/app/desktop/views/components/note-preview.vue @@ -1,8 +1,6 @@ <template> <div class="mk-note-preview" :title="title"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <header> <router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> @@ -41,18 +39,13 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 16px 0 0 - - > .avatar - display block - width 52px - height 52px - margin 0 - border-radius 8px - vertical-align bottom + width 52px + height 52px + border-radius 8px > .main float left diff --git a/src/client/app/desktop/views/components/notes.note.sub.vue b/src/client/app/desktop/views/components/notes.note.sub.vue index 4472ddefb..238fb0369 100644 --- a/src/client/app/desktop/views/components/notes.note.sub.vue +++ b/src/client/app/desktop/views/components/notes.note.sub.vue @@ -1,8 +1,6 @@ <template> <div class="sub" :title="title"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <header> <router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> @@ -53,18 +51,13 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 14px 0 0 - - > .avatar - display block - width 52px - height 52px - margin 0 - border-radius 8px - vertical-align bottom + width 52px + height 52px + border-radius 8px > .main float left diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index ee24543eb..b512f78ec 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -1,12 +1,10 @@ <template> <div class="note" tabindex="-1" :title="title" @keydown="onKeydown"> - <div class="reply-to" v-if="p.reply && (!os.isSignedIn || os.i.clientSettings.showReplyTarget)"> + <div class="reply-to" v-if="p.reply && (!os.isSignedIn || clientSettings.showReplyTarget)"> <x-sub :note="p.reply"/> </div> <div class="renote" v-if="isRenote"> - <router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> %fa:retweet% <span>{{ '%i18n:!@reposted-by%'.substr(0, '%i18n:!@reposted-by%'.indexOf('{')) }}</span> <a class="name" :href="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</a> @@ -14,9 +12,7 @@ <mk-time :time="note.createdAt"/> </div> <article> - <router-link class="avatar-anchor" :to="p.user | userPage"> - <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/> - </router-link> + <mk-avatar class="avatar" :user="p.user"/> <div class="main"> <header> <router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> @@ -182,7 +178,7 @@ export default Vue.extend({ // Draw map if (this.p.geo) { - const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true; + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).clientSettings.showMaps : true; if (shouldShowMap) { (this as any).os.getGoogleMaps().then(maps => { const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); @@ -343,15 +339,12 @@ root(isDark) color #9dbb00 background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%) - .avatar-anchor + .avatar display inline-block - - .avatar - vertical-align bottom - width 28px - height 28px - margin 0 8px 0 0 - border-radius 6px + width 28px + height 28px + margin 0 8px 0 0 + border-radius 6px [data-fa] margin-right 4px @@ -390,22 +383,17 @@ root(isDark) > .main > footer > button color isDark ? #707b97 : #888 - > .avatar-anchor + > .avatar display block float left margin 0 16px 10px 0 + width 58px + height 58px + border-radius 8px //position -webkit-sticky //position sticky //top 74px - > .avatar - display block - width 58px - height 58px - margin 0 - border-radius 8px - vertical-align bottom - > .main float left width calc(100% - 74px) diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index c4824feea..7e80e6f74 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -121,13 +121,13 @@ export default Vue.extend({ const isMyNote = note.userId == (this as any).os.i.id; const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; - if ((this as any).os.i.clientSettings.showMyRenotes === false) { + if ((this as any).clientSettings.showMyRenotes === false) { if (isMyNote && isPureRenote) { return; } } - if ((this as any).os.i.clientSettings.showRenotedMyNotes === false) { + if ((this as any).clientSettings.showRenotedMyNotes === false) { if (isPureRenote && (note.renote.userId == (this as any).os.i.id)) { return; } @@ -199,7 +199,7 @@ export default Vue.extend({ this.clearNotification(); } - if ((this as any).os.i.clientSettings.fetchOnScroll !== false) { + if ((this as any).clientSettings.fetchOnScroll !== false) { const current = window.scrollY + window.innerHeight; if (current > document.body.offsetHeight - 8) this.loadMore(); } diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue index 36e9dce6a..7923d1a62 100644 --- a/src/client/app/desktop/views/components/notifications.vue +++ b/src/client/app/desktop/views/components/notifications.vue @@ -6,9 +6,7 @@ <div class="notification" :class="notification.type" :key="notification.id"> <mk-time :time="notification.createdAt"/> <template v-if="notification.type == 'reaction'"> - <router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div class="text"> <p> <mk-reaction-icon :reaction="notification.reaction"/> @@ -20,9 +18,7 @@ </div> </template> <template v-if="notification.type == 'renote'"> - <router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> - <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.note.user"/> <div class="text"> <p>%fa:retweet% <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> @@ -33,9 +29,7 @@ </div> </template> <template v-if="notification.type == 'quote'"> - <router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> - <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.note.user"/> <div class="text"> <p>%fa:quote-left% <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> @@ -44,9 +38,7 @@ </div> </template> <template v-if="notification.type == 'follow'"> - <router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div class="text"> <p>%fa:user-plus% <router-link :to="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</router-link> @@ -54,9 +46,7 @@ </div> </template> <template v-if="notification.type == 'reply'"> - <router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> - <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.note.user"/> <div class="text"> <p>%fa:reply% <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> @@ -65,9 +55,7 @@ </div> </template> <template v-if="notification.type == 'mention'"> - <router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> - <img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.note.user"/> <div class="text"> <p>%fa:at% <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> @@ -76,9 +64,7 @@ </div> </template> <template v-if="notification.type == 'poll_vote'"> - <router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div class="text"> <p>%fa:chart-pie%<a :href="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</a></p> <router-link class="note-ref" :to="notification.note | notePage"> @@ -223,20 +209,15 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left position -webkit-sticky position sticky top 16px - - > img - display block - min-width 36px - min-height 36px - max-width 36px - max-height 36px - border-radius 6px + width 36px + height 36px + border-radius 6px > .text float right diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index 9d56042ea..af6ab1266 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -20,7 +20,7 @@ <section class="web" v-show="page == 'web'"> <h1>動作</h1> - <mk-switch v-model="os.i.clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み"> + <mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み"> <span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。</span> </mk-switch> <mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト"> @@ -41,13 +41,14 @@ </div> <div class="div"> <mk-switch v-model="darkmode" text="ダークモード"/> - <mk-switch v-model="os.i.clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/> + <mk-switch v-model="clientSettings.circleIcons" @change="onChangeCircleIcons" text="丸いアイコンを使用"/> + <mk-switch v-model="clientSettings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="ウィンドウのタイトルバーにグラデーションを使用"/> </div> - <mk-switch v-model="os.i.clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/> - <mk-switch v-model="os.i.clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="リプライ先を表示する"/> - <mk-switch v-model="os.i.clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="自分の行ったRenoteをタイムラインに表示する"/> - <mk-switch v-model="os.i.clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="Renoteされた自分の投稿をタイムラインに表示する"/> - <mk-switch v-model="os.i.clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開"> + <mk-switch v-model="clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/> + <mk-switch v-model="clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="リプライ先を表示する"/> + <mk-switch v-model="clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="自分の行ったRenoteをタイムラインに表示する"/> + <mk-switch v-model="clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="Renoteされた自分の投稿をタイムラインに表示する"/> + <mk-switch v-model="clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開"> <span>位置情報が添付された投稿のマップを自動的に展開します。</span> </mk-switch> </section> @@ -69,7 +70,7 @@ <section class="web" v-show="page == 'web'"> <h1>モバイル</h1> - <mk-switch v-model="os.i.clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/> + <mk-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/> </section> <section class="web" v-show="page == 'web'"> @@ -297,8 +298,8 @@ export default Vue.extend({ this.$emit('done'); }, onChangeFetchOnScroll(v) { - (this as any).api('i/update_client_setting', { - name: 'fetchOnScroll', + this.$store.dispatch('settings/set', { + key: 'fetchOnScroll', value: v }); }, @@ -308,50 +309,56 @@ export default Vue.extend({ }); }, onChangeDark(v) { - (this as any).api('i/update_client_setting', { - name: 'dark', + this.$store.dispatch('settings/set', { + key: 'dark', value: v }); }, onChangeShowPostFormOnTopOfTl(v) { - (this as any).api('i/update_client_setting', { - name: 'showPostFormOnTopOfTl', + this.$store.dispatch('settings/set', { + key: 'showPostFormOnTopOfTl', value: v }); }, onChangeShowReplyTarget(v) { - (this as any).api('i/update_client_setting', { - name: 'showReplyTarget', + this.$store.dispatch('settings/set', { + key: 'showReplyTarget', value: v }); }, onChangeShowMyRenotes(v) { - (this as any).api('i/update_client_setting', { - name: 'showMyRenotes', + this.$store.dispatch('settings/set', { + key: 'showMyRenotes', value: v }); }, onChangeShowRenotedMyNotes(v) { - (this as any).api('i/update_client_setting', { - name: 'showRenotedMyNotes', + this.$store.dispatch('settings/set', { + key: 'showRenotedMyNotes', value: v }); }, onChangeShowMaps(v) { - (this as any).api('i/update_client_setting', { - name: 'showMaps', + this.$store.dispatch('settings/set', { + key: 'showMaps', + value: v + }); + }, + onChangeCircleIcons(v) { + this.$store.dispatch('settings/set', { + key: 'circleIcons', value: v }); }, onChangeGradientWindowHeader(v) { - (this as any).api('i/update_client_setting', { - name: 'gradientWindowHeader', + this.$store.dispatch('settings/set', { + key: 'gradientWindowHeader', value: v }); }, onChangeDisableViaMobile(v) { - (this as any).api('i/update_client_setting', { - name: 'disableViaMobile', + this.$store.dispatch('settings/set', { + key: 'disableViaMobile', value: v }); }, diff --git a/src/client/app/desktop/views/components/timeline.core.vue b/src/client/app/desktop/views/components/timeline.core.vue index a137a5707..254a5b9d6 100644 --- a/src/client/app/desktop/views/components/timeline.core.vue +++ b/src/client/app/desktop/views/components/timeline.core.vue @@ -101,8 +101,8 @@ export default Vue.extend({ (this as any).api(this.endpoint, { limit: fetchLimit + 1, untilDate: this.date ? this.date.getTime() : undefined, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -123,8 +123,8 @@ export default Vue.extend({ (this as any).api(this.endpoint, { limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); diff --git a/src/client/app/desktop/views/components/ui.header.account.vue b/src/client/app/desktop/views/components/ui.header.account.vue index 145666b4b..897d3de81 100644 --- a/src/client/app/desktop/views/components/ui.header.account.vue +++ b/src/client/app/desktop/views/components/ui.header.account.vue @@ -2,7 +2,7 @@ <div class="account"> <button class="header" :data-active="isOpen" @click="toggle"> <span class="username">{{ os.i.username }}<template v-if="!isOpen">%fa:angle-down%</template><template v-if="isOpen">%fa:angle-up%</template></span> - <img class="avatar" :src="`${ os.i.avatarUrl }?thumbnail&size=64`" alt="avatar"/> + <mk-avatar class="avatar" :user="os.i"/> </button> <transition name="zoom-in-top"> <div class="menu" v-if="isOpen"> diff --git a/src/client/app/desktop/views/components/user-list-timeline.vue b/src/client/app/desktop/views/components/user-list-timeline.vue index ee983a969..ccdf8f64d 100644 --- a/src/client/app/desktop/views/components/user-list-timeline.vue +++ b/src/client/app/desktop/views/components/user-list-timeline.vue @@ -46,8 +46,8 @@ export default Vue.extend({ (this as any).api('notes/user-list-timeline', { listId: this.list.id, limit: fetchLimit + 1, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -66,8 +66,8 @@ export default Vue.extend({ listId: this.list.id, limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); diff --git a/src/client/app/desktop/views/components/user-preview.vue b/src/client/app/desktop/views/components/user-preview.vue index d0111d7dc..cc5e02139 100644 --- a/src/client/app/desktop/views/components/user-preview.vue +++ b/src/client/app/desktop/views/components/user-preview.vue @@ -2,9 +2,7 @@ <div class="mk-user-preview"> <template v-if="u != null"> <div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl}?thumbnail&size=512)` : ''"></div> - <router-link class="avatar" :to="u | userPage"> - <img :src="`${u.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="u" :disable-preview="true"/> <div class="title"> <router-link class="name" :to="u | userPage">{{ u | userName }}</router-link> <p class="username">@{{ u | acct }}</p> @@ -111,14 +109,10 @@ root(isDark) top 62px left 13px z-index 2 - - > img - display block - width 58px - height 58px - margin 0 - border solid 3px isDark ? #282c37 : #fff - border-radius 8px + width 58px + height 58px + border solid 3px isDark ? #282c37 : #fff + border-radius 8px > .title display block diff --git a/src/client/app/desktop/views/components/users-list.item.vue b/src/client/app/desktop/views/components/users-list.item.vue index 005c9cd6d..dbad29517 100644 --- a/src/client/app/desktop/views/components/users-list.item.vue +++ b/src/client/app/desktop/views/components/users-list.item.vue @@ -1,8 +1,6 @@ <template> <div class="root item"> - <router-link class="avatar-anchor" :to="user | userPage" v-user-preview="user.id"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="user"/> <div class="main"> <header> <router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link> @@ -35,18 +33,13 @@ export default Vue.extend({ display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 16px 0 0 - - > .avatar - display block - width 58px - height 58px - margin 0 - border-radius 8px - vertical-align bottom + width 58px + height 58px + border-radius 8px > .main float left diff --git a/src/client/app/desktop/views/components/widget-container.vue b/src/client/app/desktop/views/components/widget-container.vue index 2edba5a23..ab8327d39 100644 --- a/src/client/app/desktop/views/components/widget-container.vue +++ b/src/client/app/desktop/views/components/widget-container.vue @@ -24,8 +24,8 @@ export default Vue.extend({ computed: { withGradient(): boolean { return (this as any).os.isSignedIn - ? (this as any).os.i.clientSettings.gradientWindowHeader != null - ? (this as any).os.i.clientSettings.gradientWindowHeader + ? (this as any).clientSettings.gradientWindowHeader != null + ? (this as any).clientSettings.gradientWindowHeader : false : false; } diff --git a/src/client/app/desktop/views/components/window.vue b/src/client/app/desktop/views/components/window.vue index c9820f869..91d1a9c2b 100644 --- a/src/client/app/desktop/views/components/window.vue +++ b/src/client/app/desktop/views/components/window.vue @@ -94,8 +94,8 @@ export default Vue.extend({ }, withGradient(): boolean { return (this as any).os.isSignedIn - ? (this as any).os.i.clientSettings.gradientWindowHeader != null - ? (this as any).os.i.clientSettings.gradientWindowHeader + ? (this as any).clientSettings.gradientWindowHeader != null + ? (this as any).clientSettings.gradientWindowHeader : false : false; } diff --git a/src/client/app/desktop/views/pages/user-list.users.vue b/src/client/app/desktop/views/pages/user-list.users.vue index 63070ed60..4236cdbb1 100644 --- a/src/client/app/desktop/views/pages/user-list.users.vue +++ b/src/client/app/desktop/views/pages/user-list.users.vue @@ -8,9 +8,7 @@ <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw% %i18n:common.loading%<mk-ellipsis/></p> <template v-else-if="users.length != 0"> <div class="user" v-for="_user in users"> - <router-link class="avatar-anchor" :to="_user | userPage"> - <img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/> - </router-link> + <mk-avatar class="avatar" :user="_user"/> <div class="body"> <router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link> <p class="username">@{{ _user | acct }}</p> @@ -80,18 +78,13 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 12px 0 0 - - > .avatar - display block - width 42px - height 42px - margin 0 - border-radius 8px - vertical-align bottom + width 42px + height 42px + border-radius 8px > .body float left diff --git a/src/client/app/desktop/views/pages/user/user.friends.vue b/src/client/app/desktop/views/pages/user/user.friends.vue index 161b08d1d..4af0f0bca 100644 --- a/src/client/app/desktop/views/pages/user/user.friends.vue +++ b/src/client/app/desktop/views/pages/user/user.friends.vue @@ -4,9 +4,7 @@ <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p> <template v-if="!fetching && users.length != 0"> <div class="user" v-for="friend in users"> - <router-link class="avatar-anchor" :to="friend | userPage"> - <img class="avatar" :src="`${friend.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="friend.id"/> - </router-link> + <mk-avatar class="avatar" :user="friend"/> <div class="body"> <router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link> <p class="username">@{{ friend | acct }}</p> @@ -82,18 +80,13 @@ export default Vue.extend({ display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 12px 0 0 - - > .avatar - display block - width 42px - height 42px - margin 0 - border-radius 8px - vertical-align bottom + width 42px + height 42px + border-radius 8px > .body float left diff --git a/src/client/app/desktop/views/pages/user/user.header.vue b/src/client/app/desktop/views/pages/user/user.header.vue index 99fe0b18d..629f6d87c 100644 --- a/src/client/app/desktop/views/pages/user/user.header.vue +++ b/src/client/app/desktop/views/pages/user/user.header.vue @@ -7,7 +7,7 @@ <div class="fade"></div> </div> <div class="container"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=150`" alt="avatar"/> + <mk-avatar class="avatar" :user="user" :disable-preview="true"/> <div class="title"> <p class="name">{{ user | userName }}</p> <p class="username">@{{ user | acct }}</p> @@ -139,7 +139,6 @@ export default Vue.extend({ z-index 2 width 160px height 160px - margin 0 border solid 3px #fff border-radius 8px box-shadow 1px 1px 3px rgba(#000, 0.2) diff --git a/src/client/app/desktop/views/pages/welcome.vue b/src/client/app/desktop/views/pages/welcome.vue index 223be8301..3d6765c97 100644 --- a/src/client/app/desktop/views/pages/welcome.vue +++ b/src/client/app/desktop/views/pages/welcome.vue @@ -8,9 +8,7 @@ <p>ようこそ! <b>Misskey</b>はTwitter風ミニブログSNSです。思ったことや皆と共有したいことを投稿しましょう。タイムラインを見れば、皆の関心事をすぐにチェックすることもできます。<a :href="aboutUrl">詳しく...</a></p> <p><button class="signup" @click="signup">はじめる</button><button class="signin" @click="signin">ログイン</button></p> <div class="users"> - <router-link v-for="user in users" :key="user.id" class="avatar-anchor" :to="user | userPage" v-user-preview="user.id"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :key="user.id" :user="user"/> </div> </div> <div> diff --git a/src/client/app/desktop/views/widgets/activity.vue b/src/client/app/desktop/views/widgets/activity.vue index 0bdf4622a..1be87f590 100644 --- a/src/client/app/desktop/views/widgets/activity.vue +++ b/src/client/app/desktop/views/widgets/activity.vue @@ -22,9 +22,11 @@ export default define({ } else { this.props.design++; } + this.save(); }, viewChanged(view) { this.props.view = view; + this.save(); } } }); diff --git a/src/client/app/desktop/views/widgets/channel.vue b/src/client/app/desktop/views/widgets/channel.vue index 600cdd531..d21aed40f 100644 --- a/src/client/app/desktop/views/widgets/channel.vue +++ b/src/client/app/desktop/views/widgets/channel.vue @@ -37,6 +37,7 @@ export default define({ methods: { func() { this.props.compact = !this.props.compact; + this.save(); }, settings() { const id = window.prompt('チャンネルID'); diff --git a/src/client/app/desktop/views/widgets/messaging.vue b/src/client/app/desktop/views/widgets/messaging.vue index b71db3e08..791d2ff1b 100644 --- a/src/client/app/desktop/views/widgets/messaging.vue +++ b/src/client/app/desktop/views/widgets/messaging.vue @@ -35,6 +35,7 @@ export default define({ } else { this.props.design++; } + this.save(); } } }); diff --git a/src/client/app/desktop/views/widgets/notifications.vue b/src/client/app/desktop/views/widgets/notifications.vue index 091b0d8b9..f75a09148 100644 --- a/src/client/app/desktop/views/widgets/notifications.vue +++ b/src/client/app/desktop/views/widgets/notifications.vue @@ -23,6 +23,7 @@ export default define({ }, func() { this.props.compact = !this.props.compact; + this.save(); } } }); diff --git a/src/client/app/desktop/views/widgets/polls.vue b/src/client/app/desktop/views/widgets/polls.vue index 852bf2e3a..36fcc2063 100644 --- a/src/client/app/desktop/views/widgets/polls.vue +++ b/src/client/app/desktop/views/widgets/polls.vue @@ -39,6 +39,7 @@ export default define({ methods: { func() { this.props.compact = !this.props.compact; + this.save(); }, fetch() { this.fetching = true; diff --git a/src/client/app/desktop/views/widgets/post-form.vue b/src/client/app/desktop/views/widgets/post-form.vue index 9c2d60f9b..69b21ad37 100644 --- a/src/client/app/desktop/views/widgets/post-form.vue +++ b/src/client/app/desktop/views/widgets/post-form.vue @@ -29,6 +29,7 @@ export default define({ } else { this.props.design++; } + this.save(); }, onKeydown(e) { if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post(); diff --git a/src/client/app/desktop/views/widgets/profile.vue b/src/client/app/desktop/views/widgets/profile.vue index 7958b4829..48e7747b4 100644 --- a/src/client/app/desktop/views/widgets/profile.vue +++ b/src/client/app/desktop/views/widgets/profile.vue @@ -36,6 +36,7 @@ export default define({ } else { this.props.design++; } + this.save(); } } }); diff --git a/src/client/app/desktop/views/widgets/timemachine.vue b/src/client/app/desktop/views/widgets/timemachine.vue index 6db3b14c6..22a412040 100644 --- a/src/client/app/desktop/views/widgets/timemachine.vue +++ b/src/client/app/desktop/views/widgets/timemachine.vue @@ -22,6 +22,7 @@ export default define({ } else { this.props.design++; } + this.save(); } } }); diff --git a/src/client/app/desktop/views/widgets/trends.vue b/src/client/app/desktop/views/widgets/trends.vue index 08fa7f802..c33bf2f2f 100644 --- a/src/client/app/desktop/views/widgets/trends.vue +++ b/src/client/app/desktop/views/widgets/trends.vue @@ -38,6 +38,7 @@ export default define({ methods: { func() { this.props.compact = !this.props.compact; + this.save(); }, fetch() { this.fetching = true; diff --git a/src/client/app/desktop/views/widgets/users.vue b/src/client/app/desktop/views/widgets/users.vue index 6e326115b..328fa5669 100644 --- a/src/client/app/desktop/views/widgets/users.vue +++ b/src/client/app/desktop/views/widgets/users.vue @@ -8,9 +8,7 @@ <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <template v-else-if="users.length != 0"> <div class="user" v-for="_user in users"> - <router-link class="avatar-anchor" :to="_user | userPage"> - <img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/> - </router-link> + <mk-avatar class="avatar" :user="_user"/> <div class="body"> <router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link> <p class="username">@{{ _user | acct }}</p> @@ -48,6 +46,7 @@ export default define({ methods: { func() { this.props.compact = !this.props.compact; + this.save(); }, fetch() { this.fetching = true; @@ -88,18 +87,13 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 12px 0 0 - - > .avatar - display block - width 42px - height 42px - margin 0 - border-radius 8px - vertical-align bottom + width 42px + height 42px + border-radius 8px > .body float left diff --git a/src/client/app/init.ts b/src/client/app/init.ts index 26f5328d7..6f7ce0260 100644 --- a/src/client/app/init.ts +++ b/src/client/app/init.ts @@ -3,7 +3,7 @@ */ import Vue from 'vue'; -import Vuex from 'vuex'; +import Vuex, { mapState } from 'vuex'; import VueRouter from 'vue-router'; import VModal from 'vue-js-modal'; import * as TreeView from 'vue-json-tree-view'; @@ -41,17 +41,6 @@ require('./common/views/widgets'); // Register global filters require('./common/views/filters'); -const store = new Vuex.Store({ - state: { - uiHeaderHeight: 0 - }, - mutations: { - setUiHeaderHeight(state, height) { - state.uiHeaderHeight = height; - } - } -}); - Vue.mixin({ destroyed(this: any) { if (this.$el.parentNode) { @@ -159,20 +148,15 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API) api: os.api, apis: os.apis }; - } + }, + computed: mapState({ + clientSettings: state => state.settings.data + }) }); const app = new Vue({ - store, + store: os.store, router, - created() { - this.$watch('os.i', i => { - // キャッシュ更新 - localStorage.setItem('me', JSON.stringify(i)); - }, { - deep: true - }); - }, render: createEl => createEl(App) }); diff --git a/src/client/app/mobile/views/components/note-detail.sub.vue b/src/client/app/mobile/views/components/note-detail.sub.vue index 683b5ffd1..34ff06db3 100644 --- a/src/client/app/mobile/views/components/note-detail.sub.vue +++ b/src/client/app/mobile/views/components/note-detail.sub.vue @@ -1,8 +1,6 @@ <template> <div class="root sub"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <header> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> @@ -43,18 +41,13 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 12px 0 0 - - > .avatar - display block - width 48px - height 48px - margin 0 - border-radius 8px - vertical-align bottom + width 48px + height 48px + border-radius 8px > .main float left diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue index 36c3f30c6..b9bd9fe49 100644 --- a/src/client/app/mobile/views/components/note-detail.vue +++ b/src/client/app/mobile/views/components/note-detail.vue @@ -17,17 +17,12 @@ </div> <div class="renote" v-if="isRenote"> <p> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/> - </router-link> - %fa:retweet%<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>がRenote + <mk-avatar class="avatar" :user="note.user"/>%fa:retweet%<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>がRenote </p> </div> <article> <header> - <router-link class="avatar-anchor" :to="p.user | userPage"> - <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="p.user"/> <div> <router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> <span class="username">@{{ p.user | acct }}</span> @@ -152,7 +147,7 @@ export default Vue.extend({ // Draw map if (this.p.geo) { - const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true; + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).clientSettings.showMaps : true; if (shouldShowMap) { (this as any).os.getGoogleMaps().then(maps => { const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); @@ -262,17 +257,12 @@ root(isDark) margin 0 padding 16px 32px - .avatar-anchor + .avatar display inline-block - - .avatar - vertical-align bottom - min-width 28px - min-height 28px - max-width 28px - max-height 28px - margin 0 8px 0 0 - border-radius 6px + width 28px + height 28px + margin 0 8px 0 0 + border-radius 6px [data-fa] margin-right 4px @@ -301,21 +291,16 @@ root(isDark) display flex line-height 1.1em - > .avatar-anchor + > .avatar display block - padding 0 12px 0 0 + margin 0 12px 0 0 + width 54px + height 54px + border-radius 8px - > .avatar - display block - width 54px - height 54px - margin 0 - border-radius 8px - vertical-align bottom - - @media (min-width 500px) - width 60px - height 60px + @media (min-width 500px) + width 60px + height 60px > div diff --git a/src/client/app/mobile/views/components/note-preview.vue b/src/client/app/mobile/views/components/note-preview.vue index 41244cc75..e9fe9b1bd 100644 --- a/src/client/app/mobile/views/components/note-preview.vue +++ b/src/client/app/mobile/views/components/note-preview.vue @@ -1,8 +1,6 @@ <template> <div class="mk-note-preview"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <header> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> @@ -37,18 +35,13 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 12px 0 0 - - > .avatar - display block - width 48px - height 48px - margin 0 - border-radius 8px - vertical-align bottom + width 48px + height 48px + border-radius 8px > .main float left diff --git a/src/client/app/mobile/views/components/note.sub.vue b/src/client/app/mobile/views/components/note.sub.vue index 01f02bdb5..251b377fb 100644 --- a/src/client/app/mobile/views/components/note.sub.vue +++ b/src/client/app/mobile/views/components/note.sub.vue @@ -1,8 +1,6 @@ <template> <div class="sub"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> <div class="main"> <header> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> @@ -49,25 +47,18 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 10px 0 0 + width 44px + height 44px + border-radius 8px @media (min-width 500px) margin-right 16px - - > .avatar - display block - width 44px - height 44px - margin 0 - border-radius 8px - vertical-align bottom - - @media (min-width 500px) - width 52px - height 52px + width 52px + height 52px > .main float left diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 07e18544d..f3aab49bb 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -1,12 +1,10 @@ <template> <div class="note" :class="{ renote: isRenote }"> - <div class="reply-to" v-if="p.reply && (!os.isSignedIn || os.i.clientSettings.showReplyTarget)"> + <div class="reply-to" v-if="p.reply && (!os.isSignedIn || clientSettings.showReplyTarget)"> <x-sub :note="p.reply"/> </div> <div class="renote" v-if="isRenote"> - <router-link class="avatar-anchor" :to="note.user | userPage"> - <img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="note.user"/> %fa:retweet% <span>{{ '%i18n:!@reposted-by%'.substr(0, '%i18n:!@reposted-by%'.indexOf('{')) }}</span> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> @@ -14,9 +12,7 @@ <mk-time :time="note.createdAt"/> </div> <article> - <router-link class="avatar-anchor" :to="p.user | userPage"> - <img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="p.user"/> <div class="main"> <header> <router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> @@ -154,7 +150,7 @@ export default Vue.extend({ // Draw map if (this.p.geo) { - const shouldShowMap = (this as any).os.isSignedIn ? (this as any).os.i.clientSettings.showMaps : true; + const shouldShowMap = (this as any).os.isSignedIn ? (this as any).clientSettings.showMaps : true; if (shouldShowMap) { (this as any).os.getGoogleMaps().then(maps => { const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); @@ -268,15 +264,12 @@ root(isDark) @media (min-width 600px) padding 16px 32px - .avatar-anchor + .avatar display inline-block - - .avatar - vertical-align bottom - width 28px - height 28px - margin 0 8px 0 0 - border-radius 6px + width 28px + height 28px + margin 0 8px 0 0 + border-radius 6px [data-fa] margin-right 4px @@ -314,29 +307,22 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 10px 8px 0 + width 48px + height 48px + border-radius 6px //position -webkit-sticky //position sticky //top 62px @media (min-width 500px) margin-right 16px - - > .avatar - display block - width 48px - height 48px - margin 0 - border-radius 6px - vertical-align bottom - - @media (min-width 500px) - width 58px - height 58px - border-radius 8px + width 58px + height 58px + border-radius 8px > .main float left diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index e12dc1d04..9461fe6fe 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -116,13 +116,13 @@ export default Vue.extend({ const isMyNote = note.userId == (this as any).os.i.id; const isPureRenote = note.renoteId != null && note.text == null && note.mediaIds.length == 0 && note.poll == null; - if ((this as any).os.i.clientSettings.showMyRenotes === false) { + if ((this as any).clientSettings.showMyRenotes === false) { if (isMyNote && isPureRenote) { return; } } - if ((this as any).os.i.clientSettings.showRenotedMyNotes === false) { + if ((this as any).clientSettings.showRenotedMyNotes === false) { if (isPureRenote && (note.renote.userId == (this as any).os.i.id)) { return; } @@ -187,7 +187,7 @@ export default Vue.extend({ this.clearNotification(); } - if ((this as any).os.i.clientSettings.fetchOnScroll !== false) { + if ((this as any).clientSettings.fetchOnScroll !== false) { const current = window.scrollY + window.innerHeight; if (current > document.body.offsetHeight - 8) this.loadMore(); } diff --git a/src/client/app/mobile/views/components/notification.vue b/src/client/app/mobile/views/components/notification.vue index a4e6b027e..13ca95075 100644 --- a/src/client/app/mobile/views/components/notification.vue +++ b/src/client/app/mobile/views/components/notification.vue @@ -1,9 +1,7 @@ <template> <div class="mk-notification"> <div class="notification reaction" v-if="notification.type == 'reaction'"> - <router-link class="avatar-anchor" :to="notification.user | userPage"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div> <header> <mk-reaction-icon :reaction="notification.reaction"/> @@ -18,9 +16,7 @@ </div> <div class="notification renote" v-if="notification.type == 'renote'"> - <router-link class="avatar-anchor" :to="notification.user | userPage"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div> <header> %fa:retweet% @@ -34,9 +30,7 @@ </div> <div class="notification follow" v-if="notification.type == 'follow'"> - <router-link class="avatar-anchor" :to="notification.user | userPage"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div> <header> %fa:user-plus% @@ -47,9 +41,7 @@ </div> <div class="notification poll_vote" v-if="notification.type == 'poll_vote'"> - <router-link class="avatar-anchor" :to="notification.user | userPage"> - <img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="notification.user"/> <div> <header> %fa:chart-pie% @@ -111,18 +103,16 @@ root(isDark) display block clear both - > .avatar-anchor + > .avatar display block float left + width 36px + height 36px + border-radius 6px - img - width 36px - height 36px - border-radius 6px - - @media (min-width 500px) - width 42px - height 42px + @media (min-width 500px) + width 42px + height 42px > div float right diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index ec1611979..3f890223a 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -166,7 +166,7 @@ export default Vue.extend({ post() { this.posting = true; - const viaMobile = (this as any).os.i.clientSettings.disableViaMobile !== true; + const viaMobile = (this as any).clientSettings.disableViaMobile !== true; (this as any).api('notes/create', { text: this.text == '' ? undefined : this.text, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, diff --git a/src/client/app/mobile/views/components/user-list-timeline.vue b/src/client/app/mobile/views/components/user-list-timeline.vue index ee983a969..ccdf8f64d 100644 --- a/src/client/app/mobile/views/components/user-list-timeline.vue +++ b/src/client/app/mobile/views/components/user-list-timeline.vue @@ -46,8 +46,8 @@ export default Vue.extend({ (this as any).api('notes/user-list-timeline', { listId: this.list.id, limit: fetchLimit + 1, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -66,8 +66,8 @@ export default Vue.extend({ listId: this.list.id, limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); diff --git a/src/client/app/mobile/views/components/user-preview.vue b/src/client/app/mobile/views/components/user-preview.vue index 23a83b5e3..d25836091 100644 --- a/src/client/app/mobile/views/components/user-preview.vue +++ b/src/client/app/mobile/views/components/user-preview.vue @@ -1,8 +1,6 @@ <template> <div class="mk-user-preview"> - <router-link class="avatar-anchor" :to="user | userPage"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :user="user"/> <div class="main"> <header> <router-link class="name" :to="user | userPage">{{ user | userName }}</router-link> @@ -40,26 +38,19 @@ export default Vue.extend({ display block clear both - > .avatar-anchor + > .avatar display block float left margin 0 10px 0 0 + width 48px + height 48px + border-radius 6px @media (min-width 500px) margin-right 16px - - > .avatar - display block - width 48px - height 48px - margin 0 - border-radius 6px - vertical-align bottom - - @media (min-width 500px) - width 58px - height 58px - border-radius 8px + width 58px + height 58px + border-radius 8px > .main float left diff --git a/src/client/app/mobile/views/pages/dashboard.vue b/src/client/app/mobile/views/pages/dashboard.vue index 53fe33ee8..a5ca6cb4a 100644 --- a/src/client/app/mobile/views/pages/dashboard.vue +++ b/src/client/app/mobile/views/pages/dashboard.vue @@ -64,8 +64,8 @@ export default Vue.extend({ }; }, created() { - if ((this as any).os.i.clientSettings.mobileHome == null) { - Vue.set((this as any).os.i.clientSettings, 'mobileHome', [{ + if ((this as any).clientSettings.mobileHome == null) { + Vue.set((this as any).clientSettings, 'mobileHome', [{ name: 'calendar', id: 'a', data: {} }, { @@ -87,14 +87,14 @@ export default Vue.extend({ name: 'version', id: 'g', data: {} }]); - this.widgets = (this as any).os.i.clientSettings.mobileHome; + this.widgets = (this as any).clientSettings.mobileHome; this.saveHome(); } else { - this.widgets = (this as any).os.i.clientSettings.mobileHome; + this.widgets = (this as any).clientSettings.mobileHome; } - this.$watch('os.i.clientSettings', i => { - this.widgets = (this as any).os.i.clientSettings.mobileHome; + this.$watch('clientSettings', i => { + this.widgets = (this as any).clientSettings.mobileHome; }, { deep: true }); @@ -107,15 +107,15 @@ export default Vue.extend({ methods: { onHomeUpdated(data) { if (data.home) { - (this as any).os.i.clientSettings.mobileHome = data.home; + (this as any).clientSettings.mobileHome = data.home; this.widgets = data.home; } else { - const w = (this as any).os.i.clientSettings.mobileHome.find(w => w.id == data.id); + const w = (this as any).clientSettings.mobileHome.find(w => w.id == data.id); if (w != null) { w.data = data.data; this.$refs[w.id][0].preventSave = true; this.$refs[w.id][0].props = w.data; - this.widgets = (this as any).os.i.clientSettings.mobileHome; + this.widgets = (this as any).clientSettings.mobileHome; } } }, @@ -144,7 +144,7 @@ export default Vue.extend({ this.saveHome(); }, saveHome() { - (this as any).os.i.clientSettings.mobileHome = this.widgets; + (this as any).clientSettings.mobileHome = this.widgets; (this as any).api('i/update_mobile_home', { home: this.widgets }); diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue index 5f4bd6dcd..4c1c344db 100644 --- a/src/client/app/mobile/views/pages/home.timeline.vue +++ b/src/client/app/mobile/views/pages/home.timeline.vue @@ -92,8 +92,8 @@ export default Vue.extend({ (this as any).api(this.endpoint, { limit: fetchLimit + 1, untilDate: this.date ? this.date.getTime() : undefined, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); @@ -114,8 +114,8 @@ export default Vue.extend({ (this as any).api(this.endpoint, { limit: fetchLimit + 1, untilId: (this.$refs.timeline as any).tail().id, - includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, - includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes + includeMyRenotes: (this as any).clientSettings.showMyRenotes, + includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes }).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); diff --git a/src/client/app/mobile/views/pages/welcome.vue b/src/client/app/mobile/views/pages/welcome.vue index 485996870..4d236d7aa 100644 --- a/src/client/app/mobile/views/pages/welcome.vue +++ b/src/client/app/mobile/views/pages/welcome.vue @@ -22,9 +22,7 @@ <mk-welcome-timeline/> </div> <div class="users"> - <router-link v-for="user in users" :key="user.id" class="avatar-anchor" :to="`/@${user.username}`"> - <img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/> - </router-link> + <mk-avatar class="avatar" :key="user.id" :user="user"/> </div> <footer> <small>{{ copyright }}</small> diff --git a/src/client/app/mobile/views/widgets/activity.vue b/src/client/app/mobile/views/widgets/activity.vue index 48dcafb3e..7763be41f 100644 --- a/src/client/app/mobile/views/widgets/activity.vue +++ b/src/client/app/mobile/views/widgets/activity.vue @@ -21,6 +21,7 @@ export default define({ methods: { func() { this.props.compact = !this.props.compact; + this.save(); } } }); diff --git a/src/client/app/store.ts b/src/client/app/store.ts new file mode 100644 index 000000000..706fd6555 --- /dev/null +++ b/src/client/app/store.ts @@ -0,0 +1,90 @@ +import Vuex from 'vuex'; +import MiOS from './common/mios'; + +const defaultSettings = { + home: [], + fetchOnScroll: true, + showMaps: true, + showPostFormOnTopOfTl: false, + circleIcons: true, + gradientWindowHeader: false, + showReplyTarget: true, + showMyRenotes: true, + showRenotedMyNotes: true +}; + +export default (os: MiOS) => new Vuex.Store({ + plugins: [store => { + store.subscribe((mutation, state) => { + if (mutation.type.startsWith('settings/')) { + localStorage.setItem('settings', JSON.stringify(state.settings.data)); + } + }); + }], + + state: { + uiHeaderHeight: 0 + }, + + mutations: { + setUiHeaderHeight(state, height) { + state.uiHeaderHeight = height; + } + }, + + modules: { + settings: { + namespaced: true, + + state: { + data: defaultSettings + }, + + mutations: { + init(state, settings) { + state.data = settings; + }, + + set(state, x: { key: string; value: any }) { + state.data[x.key] = x.value; + }, + + setHome(state, data) { + state.data.home = data; + }, + + setHomeWidget(state, x) { + const w = state.data.home.find(w => w.id == x.id); + if (w) { + w.data = x.data; + } + }, + + addHomeWidget(state, widget) { + state.data.home.unshift(widget); + } + }, + + actions: { + set(ctx, x) { + ctx.commit('set', x); + + if (os.isSignedIn) { + os.api('i/update_client_setting', { + name: x.key, + value: x.value + }); + } + }, + + addHomeWidget(ctx, widget) { + ctx.commit('addHomeWidget', widget); + + os.api('i/update_home', { + home: ctx.state.data.home + }); + } + } + } + } +}); diff --git a/src/server/api/endpoints/i/update_client_setting.ts b/src/server/api/endpoints/i/update_client_setting.ts index 2edc2104d..278eb1cbe 100644 --- a/src/server/api/endpoints/i/update_client_setting.ts +++ b/src/server/api/endpoints/i/update_client_setting.ts @@ -24,16 +24,11 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { $set: x }); - // Serialize - user.clientSettings[name] = value; - const iObj = await pack(user, user, { - detail: true, - includeSecrets: true + res(); + + // Publish event + event(user._id, 'clientSettingUpdated', { + key: name, + value }); - - // Send response - res(iObj); - - // Publish i updated event - event(user._id, 'i_updated', iObj); }); diff --git a/src/server/index.ts b/src/server/index.ts index 594f40c22..4c5aab9a2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -26,9 +26,9 @@ if (process.env.NODE_ENV != 'production') { app.use(logger()); // Delay - app.use(slow({ - delay: 1000 - })); + //app.use(slow({ + // delay: 1000 + //})); } // Compress response