リファクタリングなど

This commit is contained in:
syuilo 2018-04-29 17:17:15 +09:00
parent 1625b37b44
commit 372bfaceda
64 changed files with 521 additions and 570 deletions

View file

@ -18,61 +18,65 @@ export default function<T extends object>(data: {
default: false default: false
} }
}, },
computed: { computed: {
id(): string { id(): string {
return this.widget.id; return this.widget.id;
},
props(): T {
return this.widget.data;
} }
}, },
data() { data() {
return { return {
props: data.props ? data.props() : {} as T, bakedOldProps: null
bakedOldProps: null,
preventSave: false
}; };
}, },
created() { created() {
if (this.props) { this.mergeProps();
Object.keys(this.props).forEach(prop => {
if (this.widget.data.hasOwnProperty(prop)) { this.$watch('props', () => {
this.props[prop] = this.widget.data[prop]; this.mergeProps();
});
this.bakeProps();
},
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]);
} }
}); });
} }
},
this.bakeProps(); save() {
if (this.bakedOldProps == JSON.stringify(this.props)) return;
this.$watch('props', newProps => {
if (this.preventSave) {
this.preventSave = false;
this.bakeProps();
return;
}
if (this.bakedOldProps == JSON.stringify(newProps)) return;
this.bakeProps(); this.bakeProps();
if (this.isMobile) { if (this.isMobile) {
(this as any).api('i/update_mobile_home', { (this as any).api('i/update_mobile_home', {
id: this.id, id: this.id,
data: newProps data: this.props
}).then(() => {
(this as any).os.i.clientSettings.mobileHome.find(w => w.id == this.id).data = newProps;
}); });
} else { } else {
(this as any).api('i/update_home', { (this as any).api('i/update_home', {
id: this.id, id: this.id,
data: newProps data: this.props
}).then(() => {
(this as any).os.i.clientSettings.home.find(w => w.id == this.id).data = newProps;
}); });
} }
}, {
deep: true
});
},
methods: {
bakeProps() {
this.bakedOldProps = JSON.stringify(this.props);
} }
} }
}); });

View file

@ -3,6 +3,7 @@ import { EventEmitter } from 'eventemitter3';
import * as merge from 'object-assign-deep'; import * as merge from 'object-assign-deep';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import initStore from '../store';
import { hostname, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config'; import { hostname, apiUrl, swPublickey, version, lang, googleMapsApiKey } from '../config';
import Progress from './scripts/loading'; import Progress from './scripts/loading';
import Connection from './scripts/streaming/stream'; 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 { LocalTimelineStreamManager } from './scripts/streaming/local-timeline';
import { GlobalTimelineStreamManager } from './scripts/streaming/global-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 //#region api requests
let spinner = null; let spinner = null;
let pending = 0; let pending = 0;
@ -117,6 +108,8 @@ export default class MiOS extends EventEmitter {
return localStorage.getItem('enableSounds') == 'true'; return localStorage.getItem('enableSounds') == 'true';
} }
public store: ReturnType<typeof initStore>;
public apis: API; public apis: API;
/** /**
@ -232,6 +225,11 @@ export default class MiOS extends EventEmitter {
console.error.apply(null, args); console.error.apply(null, args);
} }
public bakeMe() {
// ローカルストレージにキャッシュ
localStorage.setItem('me', JSON.stringify(this.i));
}
public signout() { public signout() {
localStorage.removeItem('me'); localStorage.removeItem('me');
document.cookie = `i=; domain=${hostname}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; 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 * @param callback A function that call when initialized
*/ */
public async init(callback) { public async init(callback) {
this.store = initStore(this);
//#region Init stream managers //#region Init stream managers
this.streams.serverStream = new ServerStreamManager(this); this.streams.serverStream = new ServerStreamManager(this);
@ -307,15 +307,10 @@ export default class MiOS extends EventEmitter {
// フェッチが完了したとき // フェッチが完了したとき
const fetched = me => { const fetched = me => {
if (me) { this.i = me;
// デフォルトの設定をマージ
me.clientSettings = Object.assign(defaultSettings, me.clientSettings);
// ローカルストレージにキャッシュ // ローカルストレージにキャッシュ
localStorage.setItem('me', JSON.stringify(me)); this.bakeMe();
}
this.i = me;
this.emit('signedin'); this.emit('signedin');
@ -333,6 +328,14 @@ export default class MiOS extends EventEmitter {
// Get cached account data // Get cached account data
const cachedMe = JSON.parse(localStorage.getItem('me')); 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) {
if (cachedMe.token == null) { if (cachedMe.token == null) {
@ -346,12 +349,25 @@ export default class MiOS extends EventEmitter {
// 後から新鮮なデータをフェッチ // 後から新鮮なデータをフェッチ
fetchme(cachedMe.token, freshData => { fetchme(cachedMe.token, freshData => {
merge(cachedMe, freshData); merge(cachedMe, freshData);
this.store.commit('settings/init', freshData.clientSettings);
}); });
} else { } else {
// Get token from cookie // Get token from cookie
const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1]; 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 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); (localStorage.getItem('apiViaStream') ? localStorage.getItem('apiViaStream') == 'true' : true);
if (viaStream) { if (viaStream) {

View file

@ -25,10 +25,31 @@ export class HomeStream extends Stream {
console.log('I updated:', i); console.log('I updated:', i);
} }
merge(me, 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', () => { this.on('my_token_regenerated', () => {
alert('%i18n:!common.my-token-regenerated%'); alert('%i18n:!common.my-token-regenerated%');
os.signout(); os.signout();

View file

@ -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>

View file

@ -3,6 +3,7 @@ import Vue from 'vue';
import signin from './signin.vue'; import signin from './signin.vue';
import signup from './signup.vue'; import signup from './signup.vue';
import forkit from './forkit.vue'; import forkit from './forkit.vue';
import avatar from './avatar.vue';
import nav from './nav.vue'; import nav from './nav.vue';
import noteHtml from './note-html'; import noteHtml from './note-html';
import poll from './poll.vue'; import poll from './poll.vue';
@ -28,6 +29,7 @@ import welcomeTimeline from './welcome-timeline.vue';
Vue.component('mk-signin', signin); Vue.component('mk-signin', signin);
Vue.component('mk-signup', signup); Vue.component('mk-signup', signup);
Vue.component('mk-forkit', forkit); Vue.component('mk-forkit', forkit);
Vue.component('mk-avatar', avatar);
Vue.component('mk-nav', nav); Vue.component('mk-nav', nav);
Vue.component('mk-note-html', noteHtml); Vue.component('mk-note-html', noteHtml);
Vue.component('mk-poll', poll); Vue.component('mk-poll', poll);

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="message" :data-is-me="isMe"> <div class="message" :data-is-me="isMe">
<router-link class="avatar-anchor" :to="message.user | userPage" :title="message.user | acct" target="_blank"> <mk-avatar class="avatar" :user="message.user" target="_blank"/>
<img class="avatar" :src="`${message.user.avatarUrl}?thumbnail&size=80`" alt=""/>
</router-link>
<div class="content"> <div class="content">
<div class="balloon" :data-no-text="message.text == null"> <div class="balloon" :data-no-text="message.text == null">
<p class="read" v-if="isMe && message.isRead">%i18n:@is-read%</p> <p class="read" v-if="isMe && message.isRead">%i18n:@is-read%</p>
@ -67,18 +65,12 @@ export default Vue.extend({
padding 10px 12px 10px 12px padding 10px 12px 10px 12px
background-color transparent background-color transparent
> .avatar-anchor > .avatar
display block display block
position absolute position absolute
top 10px top 10px
width 54px
> .avatar height 54px
display block
min-width 54px
min-height 54px
max-width 54px
max-height 54px
margin 0
border-radius 8px border-radius 8px
transition all 0.1s ease transition all 0.1s ease
@ -201,7 +193,7 @@ export default Vue.extend({
margin-left 4px margin-left 4px
&:not([data-is-me]) &:not([data-is-me])
> .avatar-anchor > .avatar
left 12px left 12px
> .content > .content
@ -225,7 +217,7 @@ export default Vue.extend({
text-align left text-align left
&[data-is-me] &[data-is-me]
> .avatar-anchor > .avatar
right 12px right 12px
> .content > .content

View file

@ -13,7 +13,7 @@
@click="navigate(user)" @click="navigate(user)"
tabindex="-1" 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="name">{{ user | userName }}</span>
<span class="username">@{{ user | acct }}</span> <span class="username">@{{ user | acct }}</span>
</li> </li>
@ -31,7 +31,7 @@
:key="message.id" :key="message.id"
> >
<div> <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> <header>
<span class="name">{{ isMe(message) ? message.recipient : message.user | userName }}</span> <span class="name">{{ isMe(message) ? message.recipient : message.user | userName }}</span>
<span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span> <span class="username">@{{ isMe(message) ? message.recipient : message.user | acct }}</span>

View file

@ -1,9 +1,7 @@
<template> <template>
<div class="mk-welcome-timeline"> <div class="mk-welcome-timeline">
<div v-for="note in notes"> <div v-for="note in notes">
<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.user.id"> <mk-avatar class="avatar" :user="note.user" target="_blank"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
</router-link>
<div class="body"> <div class="body">
<header> <header>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage" v-user-preview="note.user.id">{{ note.user | userName }}</router-link>
@ -69,15 +67,12 @@ export default Vue.extend({
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
position -webkit-sticky position -webkit-sticky
position sticky position sticky
top 16px top 16px
> img
display block
width 42px width 42px
height 42px height 42px
border-radius 6px border-radius 6px

View file

@ -61,6 +61,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -68,6 +68,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -73,6 +73,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
}, },
tick() { tick() {
const now = new Date(); const now = new Date();

View file

@ -59,6 +59,8 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -40,6 +40,7 @@ export default define({
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
}, },
fetch() { fetch() {
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, { fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, {

View file

@ -68,6 +68,7 @@ export default define({
} else { } else {
this.props.view++; this.props.view++;
} }
this.save();
}, },
func() { func() {
if (this.props.design == 2) { if (this.props.design == 2) {
@ -75,6 +76,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -64,6 +64,7 @@ export default define({
} else { } else {
this.props.size++; this.props.size++;
} }
this.save();
this.applySize(); this.applySize();
}, },
@ -111,6 +112,7 @@ export default define({
choose() { choose() {
(this as any).apis.chooseDriveFolder().then(folder => { (this as any).apis.chooseDriveFolder().then(folder => {
this.props.folder = folder ? folder.id : null; this.props.folder = folder ? folder.id : null;
this.save();
this.fetch(); this.fetch();
}); });
} }

View file

@ -3,9 +3,7 @@
<p class="title">気になるユーザーをフォロー:</p> <p class="title">気になるユーザーをフォロー:</p>
<div class="users" v-if="!fetching && users.length > 0"> <div class="users" v-if="!fetching && users.length > 0">
<div class="user" v-for="user in users" :key="user.id"> <div class="user" v-for="user in users" :key="user.id">
<router-link class="avatar-anchor" :to="user | userPage"> <mk-avatar class="avatar" :user="user" target="_blank"/>
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="user.id"/>
</router-link>
<div class="body"> <div class="body">
<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link> <router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link>
<p class="username">@{{ user | acct }}</p> <p class="username">@{{ user | acct }}</p>
@ -86,18 +84,13 @@ export default Vue.extend({
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 12px 0 0 margin 0 12px 0 0
> .avatar
display block
width 42px width 42px
height 42px height 42px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .body > .body
float left float left

View file

@ -53,7 +53,7 @@
<div class="main"> <div class="main">
<a @click="hint">カスタマイズのヒント</a> <a @click="hint">カスタマイズのヒント</a>
<div> <div>
<mk-post-form v-if="os.i.clientSettings.showPostFormOnTopOfTl"/> <mk-post-form v-if="clientSettings.showPostFormOnTopOfTl"/>
<mk-timeline ref="tl" @loaded="onTlLoaded"/> <mk-timeline ref="tl" @loaded="onTlLoaded"/>
</div> </div>
</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"/> <component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/>
</div> </div>
<div class="main"> <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-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/>
<mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/> <mk-mentions @loaded="onTlLoaded" v-if="mode == 'mentions'"/>
</div> </div>
@ -81,6 +81,7 @@ export default Vue.extend({
components: { components: {
XDraggable XDraggable
}, },
props: { props: {
customize: { customize: {
type: Boolean, type: Boolean,
@ -91,61 +92,43 @@ export default Vue.extend({
default: 'timeline' default: 'timeline'
} }
}, },
data() { data() {
return { return {
connection: null, connection: null,
connectionId: null, connectionId: null,
widgetAdderSelected: null, widgetAdderSelected: null,
trash: [], trash: []
widgets: {
left: [],
right: []
}
}; };
}, },
computed: { computed: {
home: { home(): any[] {
get(): any[] { return this.$store.state.settings.data.home;
//#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;
}
}, },
left(): any[] { left(): any[] {
return this.home.filter(w => w.place == 'left'); return this.home.filter(w => w.place == 'left');
}, },
right(): any[] { right(): any[] {
return this.home.filter(w => w.place == 'right'); 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() { mounted() {
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
this.connection.on('home_updated', this.onHomeUpdated);
}, },
beforeDestroy() { beforeDestroy() {
this.connection.off('home_updated', this.onHomeUpdated);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
}, },
methods: { methods: {
hint() { hint() {
(this as any).apis.dialog({ (this as any).apis.dialog({
@ -159,56 +142,44 @@ export default Vue.extend({
}] }]
}); });
}, },
onTlLoaded() { onTlLoaded() {
this.$emit('loaded'); 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) { onWidgetContextmenu(widgetId) {
const w = (this.$refs[widgetId] as any)[0]; const w = (this.$refs[widgetId] as any)[0];
if (w.func) w.func(); if (w.func) w.func();
}, },
onWidgetSort() { onWidgetSort() {
this.saveHome(); this.saveHome();
}, },
onTrash(evt) { onTrash(evt) {
this.saveHome(); this.saveHome();
}, },
addWidget() { addWidget() {
const widget = { this.$store.dispatch('settings/addHomeWidget', {
name: this.widgetAdderSelected, name: this.widgetAdderSelected,
id: uuid(), id: uuid(),
place: 'left', place: 'left',
data: {} data: {}
}; });
this.widgets.left.unshift(widget);
this.saveHome();
}, },
saveHome() { saveHome() {
const left = this.widgets.left; const left = this.widgets.left;
const right = this.widgets.right; const right = this.widgets.right;
this.home = left.concat(right); this.$store.commit('settings/setHome', left.concat(right));
left.forEach(w => w.place = 'left'); left.forEach(w => w.place = 'left');
right.forEach(w => w.place = 'right'); right.forEach(w => w.place = 'right');
(this as any).api('i/update_home', { (this as any).api('i/update_home', {
home: this.home home: this.home
}); });
}, },
warp(date) { warp(date) {
(this.$refs.tl as any).warp(date); (this.$refs.tl as any).warp(date);
} }

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="sub" :title="title"> <div class="sub" :title="title">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<div class="left"> <div class="left">
@ -57,18 +55,13 @@ root(isDark)
> .main > footer > button > .main > footer > button
color #888 color #888
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 16px 0 0 margin 0 16px 0 0
> .avatar
display block
width 44px width 44px
height 44px height 44px
margin 0
border-radius 4px border-radius 4px
vertical-align bottom
> .main > .main
float left float left

View file

@ -18,18 +18,14 @@
</div> </div>
<div class="renote" v-if="isRenote"> <div class="renote" v-if="isRenote">
<p> <p>
<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
</router-link>
%fa:retweet% %fa:retweet%
<router-link class="name" :href="note.user | userPage">{{ note.user | userName }}</router-link> <router-link class="name" :href="note.user | userPage">{{ note.user | userName }}</router-link>
がRenote がRenote
</p> </p>
</div> </div>
<article> <article>
<router-link class="avatar-anchor" :to="p.user | userPage"> <mk-avatar class="avatar" :user="p.user"/>
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/>
</router-link>
<header> <header>
<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> <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> <span class="username">@{{ p.user | acct }}</span>
@ -159,7 +155,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { 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) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
@ -262,15 +258,10 @@ root(isDark)
margin 0 margin 0
padding 16px 32px padding 16px 32px
.avatar-anchor
display inline-block
.avatar .avatar
vertical-align bottom display inline-block
min-width 28px width 28px
min-height 28px height 28px
max-width 28px
max-height 28px
margin 0 8px 0 0 margin 0 8px 0 0
border-radius 6px border-radius 6px
@ -298,18 +289,10 @@ root(isDark)
> footer > button > footer > button
color isDark ? #707b97 : #888 color isDark ? #707b97 : #888
> .avatar-anchor
display block
width 60px
height 60px
> .avatar > .avatar
display block
width 60px width 60px
height 60px height 60px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> header > header
position absolute position absolute

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="mk-note-preview" :title="title"> <div class="mk-note-preview" :title="title">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> <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 display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 16px 0 0 margin 0 16px 0 0
> .avatar
display block
width 52px width 52px
height 52px height 52px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .main > .main
float left float left

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="sub" :title="title"> <div class="sub" :title="title">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="note.userId"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage" v-user-preview="note.userId">{{ note.user | userName }}</router-link> <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 display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 14px 0 0 margin 0 14px 0 0
> .avatar
display block
width 52px width 52px
height 52px height 52px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .main > .main
float left float left

View file

@ -1,12 +1,10 @@
<template> <template>
<div class="note" tabindex="-1" :title="title" @keydown="onKeydown"> <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"/> <x-sub :note="p.reply"/>
</div> </div>
<div class="renote" v-if="isRenote"> <div class="renote" v-if="isRenote">
<router-link class="avatar-anchor" :to="note.user | userPage" v-user-preview="note.userId"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=32`" alt="avatar"/>
</router-link>
%fa:retweet% %fa:retweet%
<span>{{ '%i18n:!@reposted-by%'.substr(0, '%i18n:!@reposted-by%'.indexOf('{')) }}</span> <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> <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"/> <mk-time :time="note.createdAt"/>
</div> </div>
<article> <article>
<router-link class="avatar-anchor" :to="p.user | userPage"> <mk-avatar class="avatar" :user="p.user"/>
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar" v-user-preview="p.user.id"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="p.user | userPage" v-user-preview="p.user.id">{{ p.user | userName }}</router-link> <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 // Draw map
if (this.p.geo) { 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) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
@ -343,11 +339,8 @@ root(isDark)
color #9dbb00 color #9dbb00
background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%) background isDark ? linear-gradient(to bottom, #314027 0%, #282c37 100%) : linear-gradient(to bottom, #edfde2 0%, #fff 100%)
.avatar-anchor
display inline-block
.avatar .avatar
vertical-align bottom display inline-block
width 28px width 28px
height 28px height 28px
margin 0 8px 0 0 margin 0 8px 0 0
@ -390,22 +383,17 @@ root(isDark)
> .main > footer > button > .main > footer > button
color isDark ? #707b97 : #888 color isDark ? #707b97 : #888
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 16px 10px 0 margin 0 16px 10px 0
width 58px
height 58px
border-radius 8px
//position -webkit-sticky //position -webkit-sticky
//position sticky //position sticky
//top 74px //top 74px
> .avatar
display block
width 58px
height 58px
margin 0
border-radius 8px
vertical-align bottom
> .main > .main
float left float left
width calc(100% - 74px) width calc(100% - 74px)

View file

@ -121,13 +121,13 @@ export default Vue.extend({
const isMyNote = note.userId == (this as any).os.i.id; 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; 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) { if (isMyNote && isPureRenote) {
return; 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)) { if (isPureRenote && (note.renote.userId == (this as any).os.i.id)) {
return; return;
} }
@ -199,7 +199,7 @@ export default Vue.extend({
this.clearNotification(); this.clearNotification();
} }
if ((this as any).os.i.clientSettings.fetchOnScroll !== false) { if ((this as any).clientSettings.fetchOnScroll !== false) {
const current = window.scrollY + window.innerHeight; const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.loadMore(); if (current > document.body.offsetHeight - 8) this.loadMore();
} }

View file

@ -6,9 +6,7 @@
<div class="notification" :class="notification.type" :key="notification.id"> <div class="notification" :class="notification.type" :key="notification.id">
<mk-time :time="notification.createdAt"/> <mk-time :time="notification.createdAt"/>
<template v-if="notification.type == 'reaction'"> <template v-if="notification.type == 'reaction'">
<router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p> <p>
<mk-reaction-icon :reaction="notification.reaction"/> <mk-reaction-icon :reaction="notification.reaction"/>
@ -20,9 +18,7 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'renote'"> <template v-if="notification.type == 'renote'">
<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> <mk-avatar class="avatar" :user="notification.note.user"/>
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p>%fa:retweet% <p>%fa:retweet%
<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link>
@ -33,9 +29,7 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'quote'"> <template v-if="notification.type == 'quote'">
<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> <mk-avatar class="avatar" :user="notification.note.user"/>
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p>%fa:quote-left% <p>%fa:quote-left%
<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link>
@ -44,9 +38,7 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'follow'"> <template v-if="notification.type == 'follow'">
<router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p>%fa:user-plus% <p>%fa:user-plus%
<router-link :to="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</router-link> <router-link :to="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</router-link>
@ -54,9 +46,7 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'reply'"> <template v-if="notification.type == 'reply'">
<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> <mk-avatar class="avatar" :user="notification.note.user"/>
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p>%fa:reply% <p>%fa:reply%
<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link>
@ -65,9 +55,7 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'mention'"> <template v-if="notification.type == 'mention'">
<router-link class="avatar-anchor" :to="notification.note.user | userPage" v-user-preview="notification.note.userId"> <mk-avatar class="avatar" :user="notification.note.user"/>
<img class="avatar" :src="`${notification.note.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p>%fa:at% <p>%fa:at%
<router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link> <router-link :to="notification.note.user | userPage" v-user-preview="notification.note.userId">{{ notification.note.user | userName }}</router-link>
@ -76,9 +64,7 @@
</div> </div>
</template> </template>
<template v-if="notification.type == 'poll_vote'"> <template v-if="notification.type == 'poll_vote'">
<router-link class="avatar-anchor" :to="notification.user | userPage" v-user-preview="notification.user.id"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=48`" alt="avatar"/>
</router-link>
<div class="text"> <div class="text">
<p>%fa:chart-pie%<a :href="notification.user | userPage" v-user-preview="notification.user.id">{{ notification.user | userName }}</a></p> <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"> <router-link class="note-ref" :to="notification.note | notePage">
@ -223,19 +209,14 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
position -webkit-sticky position -webkit-sticky
position sticky position sticky
top 16px top 16px
width 36px
> img height 36px
display block
min-width 36px
min-height 36px
max-width 36px
max-height 36px
border-radius 6px border-radius 6px
> .text > .text

View file

@ -20,7 +20,7 @@
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
<h1>動作</h1> <h1>動作</h1>
<mk-switch v-model="os.i.clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み"> <mk-switch v-model="clientSettings.fetchOnScroll" @change="onChangeFetchOnScroll" text="スクロールで自動読み込み">
<span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます</span> <span>ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます</span>
</mk-switch> </mk-switch>
<mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト"> <mk-switch v-model="autoPopout" text="ウィンドウの自動ポップアウト">
@ -41,13 +41,14 @@
</div> </div>
<div class="div"> <div class="div">
<mk-switch v-model="darkmode" text="ダークモード"/> <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> </div>
<mk-switch v-model="os.i.clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/> <mk-switch v-model="clientSettings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="タイムライン上部に投稿フォームを表示する"/>
<mk-switch v-model="os.i.clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="リプライ先を表示する"/> <mk-switch v-model="clientSettings.showReplyTarget" @change="onChangeShowReplyTarget" text="リプライ先を表示する"/>
<mk-switch v-model="os.i.clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="自分の行ったRenoteをタイムラインに表示する"/> <mk-switch v-model="clientSettings.showMyRenotes" @change="onChangeShowMyRenotes" text="自分の行ったRenoteをタイムラインに表示する"/>
<mk-switch v-model="os.i.clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="Renoteされた自分の投稿をタイムラインに表示する"/> <mk-switch v-model="clientSettings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes" text="Renoteされた自分の投稿をタイムラインに表示する"/>
<mk-switch v-model="os.i.clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開"> <mk-switch v-model="clientSettings.showMaps" @change="onChangeShowMaps" text="マップの自動展開">
<span>位置情報が添付された投稿のマップを自動的に展開します</span> <span>位置情報が添付された投稿のマップを自動的に展開します</span>
</mk-switch> </mk-switch>
</section> </section>
@ -69,7 +70,7 @@
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
<h1>モバイル</h1> <h1>モバイル</h1>
<mk-switch v-model="os.i.clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/> <mk-switch v-model="clientSettings.disableViaMobile" @change="onChangeDisableViaMobile" text="「モバイルからの投稿」フラグを付けない"/>
</section> </section>
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
@ -297,8 +298,8 @@ export default Vue.extend({
this.$emit('done'); this.$emit('done');
}, },
onChangeFetchOnScroll(v) { onChangeFetchOnScroll(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'fetchOnScroll', key: 'fetchOnScroll',
value: v value: v
}); });
}, },
@ -308,50 +309,56 @@ export default Vue.extend({
}); });
}, },
onChangeDark(v) { onChangeDark(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'dark', key: 'dark',
value: v value: v
}); });
}, },
onChangeShowPostFormOnTopOfTl(v) { onChangeShowPostFormOnTopOfTl(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'showPostFormOnTopOfTl', key: 'showPostFormOnTopOfTl',
value: v value: v
}); });
}, },
onChangeShowReplyTarget(v) { onChangeShowReplyTarget(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'showReplyTarget', key: 'showReplyTarget',
value: v value: v
}); });
}, },
onChangeShowMyRenotes(v) { onChangeShowMyRenotes(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'showMyRenotes', key: 'showMyRenotes',
value: v value: v
}); });
}, },
onChangeShowRenotedMyNotes(v) { onChangeShowRenotedMyNotes(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'showRenotedMyNotes', key: 'showRenotedMyNotes',
value: v value: v
}); });
}, },
onChangeShowMaps(v) { onChangeShowMaps(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'showMaps', key: 'showMaps',
value: v
});
},
onChangeCircleIcons(v) {
this.$store.dispatch('settings/set', {
key: 'circleIcons',
value: v value: v
}); });
}, },
onChangeGradientWindowHeader(v) { onChangeGradientWindowHeader(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'gradientWindowHeader', key: 'gradientWindowHeader',
value: v value: v
}); });
}, },
onChangeDisableViaMobile(v) { onChangeDisableViaMobile(v) {
(this as any).api('i/update_client_setting', { this.$store.dispatch('settings/set', {
name: 'disableViaMobile', key: 'disableViaMobile',
value: v value: v
}); });
}, },

View file

@ -101,8 +101,8 @@ export default Vue.extend({
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, {
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : undefined, untilDate: this.date ? this.date.getTime() : undefined,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@ -123,8 +123,8 @@ export default Vue.extend({
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, {
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();

View file

@ -2,7 +2,7 @@
<div class="account"> <div class="account">
<button class="header" :data-active="isOpen" @click="toggle"> <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> <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> </button>
<transition name="zoom-in-top"> <transition name="zoom-in-top">
<div class="menu" v-if="isOpen"> <div class="menu" v-if="isOpen">

View file

@ -46,8 +46,8 @@ export default Vue.extend({
(this as any).api('notes/user-list-timeline', { (this as any).api('notes/user-list-timeline', {
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@ -66,8 +66,8 @@ export default Vue.extend({
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();

View file

@ -2,9 +2,7 @@
<div class="mk-user-preview"> <div class="mk-user-preview">
<template v-if="u != null"> <template v-if="u != null">
<div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl}?thumbnail&size=512)` : ''"></div> <div class="banner" :style="u.bannerUrl ? `background-image: url(${u.bannerUrl}?thumbnail&size=512)` : ''"></div>
<router-link class="avatar" :to="u | userPage"> <mk-avatar class="avatar" :user="u" :disable-preview="true"/>
<img :src="`${u.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div class="title"> <div class="title">
<router-link class="name" :to="u | userPage">{{ u | userName }}</router-link> <router-link class="name" :to="u | userPage">{{ u | userName }}</router-link>
<p class="username">@{{ u | acct }}</p> <p class="username">@{{ u | acct }}</p>
@ -111,12 +109,8 @@ root(isDark)
top 62px top 62px
left 13px left 13px
z-index 2 z-index 2
> img
display block
width 58px width 58px
height 58px height 58px
margin 0
border solid 3px isDark ? #282c37 : #fff border solid 3px isDark ? #282c37 : #fff
border-radius 8px border-radius 8px

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="root item"> <div class="root item">
<router-link class="avatar-anchor" :to="user | userPage" v-user-preview="user.id"> <mk-avatar class="avatar" :user="user"/>
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="user | userPage" v-user-preview="user.id">{{ user | userName }}</router-link> <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 display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 16px 0 0 margin 0 16px 0 0
> .avatar
display block
width 58px width 58px
height 58px height 58px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .main > .main
float left float left

View file

@ -24,8 +24,8 @@ export default Vue.extend({
computed: { computed: {
withGradient(): boolean { withGradient(): boolean {
return (this as any).os.isSignedIn return (this as any).os.isSignedIn
? (this as any).os.i.clientSettings.gradientWindowHeader != null ? (this as any).clientSettings.gradientWindowHeader != null
? (this as any).os.i.clientSettings.gradientWindowHeader ? (this as any).clientSettings.gradientWindowHeader
: false : false
: false; : false;
} }

View file

@ -94,8 +94,8 @@ export default Vue.extend({
}, },
withGradient(): boolean { withGradient(): boolean {
return (this as any).os.isSignedIn return (this as any).os.isSignedIn
? (this as any).os.i.clientSettings.gradientWindowHeader != null ? (this as any).clientSettings.gradientWindowHeader != null
? (this as any).os.i.clientSettings.gradientWindowHeader ? (this as any).clientSettings.gradientWindowHeader
: false : false
: false; : false;
} }

View file

@ -8,9 +8,7 @@
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw% %i18n:common.loading%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw% %i18n:common.loading%<mk-ellipsis/></p>
<template v-else-if="users.length != 0"> <template v-else-if="users.length != 0">
<div class="user" v-for="_user in users"> <div class="user" v-for="_user in users">
<router-link class="avatar-anchor" :to="_user | userPage"> <mk-avatar class="avatar" :user="_user"/>
<img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/>
</router-link>
<div class="body"> <div class="body">
<router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link> <router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link>
<p class="username">@{{ _user | acct }}</p> <p class="username">@{{ _user | acct }}</p>
@ -80,18 +78,13 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 12px 0 0 margin 0 12px 0 0
> .avatar
display block
width 42px width 42px
height 42px height 42px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .body > .body
float left float left

View file

@ -4,9 +4,7 @@
<p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p> <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:@loading%<mk-ellipsis/></p>
<template v-if="!fetching && users.length != 0"> <template v-if="!fetching && users.length != 0">
<div class="user" v-for="friend in users"> <div class="user" v-for="friend in users">
<router-link class="avatar-anchor" :to="friend | userPage"> <mk-avatar class="avatar" :user="friend"/>
<img class="avatar" :src="`${friend.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="friend.id"/>
</router-link>
<div class="body"> <div class="body">
<router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link> <router-link class="name" :to="friend | userPage" v-user-preview="friend.id">{{ friend.name }}</router-link>
<p class="username">@{{ friend | acct }}</p> <p class="username">@{{ friend | acct }}</p>
@ -82,18 +80,13 @@ export default Vue.extend({
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 12px 0 0 margin 0 12px 0 0
> .avatar
display block
width 42px width 42px
height 42px height 42px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .body > .body
float left float left

View file

@ -7,7 +7,7 @@
<div class="fade"></div> <div class="fade"></div>
</div> </div>
<div class="container"> <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"> <div class="title">
<p class="name">{{ user | userName }}</p> <p class="name">{{ user | userName }}</p>
<p class="username">@{{ user | acct }}</p> <p class="username">@{{ user | acct }}</p>
@ -139,7 +139,6 @@ export default Vue.extend({
z-index 2 z-index 2
width 160px width 160px
height 160px height 160px
margin 0
border solid 3px #fff border solid 3px #fff
border-radius 8px border-radius 8px
box-shadow 1px 1px 3px rgba(#000, 0.2) box-shadow 1px 1px 3px rgba(#000, 0.2)

View file

@ -8,9 +8,7 @@
<p>ようこそ <b>Misskey</b>はTwitter風ミニブログSNSです思ったことや皆と共有したいことを投稿しましょうタイムラインを見れば皆の関心事をすぐにチェックすることもできます<a :href="aboutUrl">詳しく...</a></p> <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> <p><button class="signup" @click="signup">はじめる</button><button class="signin" @click="signin">ログイン</button></p>
<div class="users"> <div class="users">
<router-link v-for="user in users" :key="user.id" class="avatar-anchor" :to="user | userPage" v-user-preview="user.id"> <mk-avatar class="avatar" :key="user.id" :user="user"/>
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
</div> </div>
</div> </div>
<div> <div>

View file

@ -22,9 +22,11 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
}, },
viewChanged(view) { viewChanged(view) {
this.props.view = view; this.props.view = view;
this.save();
} }
} }
}); });

View file

@ -37,6 +37,7 @@ export default define({
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
}, },
settings() { settings() {
const id = window.prompt('チャンネルID'); const id = window.prompt('チャンネルID');

View file

@ -35,6 +35,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -23,6 +23,7 @@ export default define({
}, },
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
} }
} }
}); });

View file

@ -39,6 +39,7 @@ export default define({
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
}, },
fetch() { fetch() {
this.fetching = true; this.fetching = true;

View file

@ -29,6 +29,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
}, },
onKeydown(e) { onKeydown(e) {
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post(); if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();

View file

@ -36,6 +36,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -22,6 +22,7 @@ export default define({
} else { } else {
this.props.design++; this.props.design++;
} }
this.save();
} }
} }
}); });

View file

@ -38,6 +38,7 @@ export default define({
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
}, },
fetch() { fetch() {
this.fetching = true; this.fetching = true;

View file

@ -8,9 +8,7 @@
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<template v-else-if="users.length != 0"> <template v-else-if="users.length != 0">
<div class="user" v-for="_user in users"> <div class="user" v-for="_user in users">
<router-link class="avatar-anchor" :to="_user | userPage"> <mk-avatar class="avatar" :user="_user"/>
<img class="avatar" :src="`${_user.avatarUrl}?thumbnail&size=42`" alt="" v-user-preview="_user.id"/>
</router-link>
<div class="body"> <div class="body">
<router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link> <router-link class="name" :to="_user | userPage" v-user-preview="_user.id">{{ _user | userName }}</router-link>
<p class="username">@{{ _user | acct }}</p> <p class="username">@{{ _user | acct }}</p>
@ -48,6 +46,7 @@ export default define({
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
}, },
fetch() { fetch() {
this.fetching = true; this.fetching = true;
@ -88,18 +87,13 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 12px 0 0 margin 0 12px 0 0
> .avatar
display block
width 42px width 42px
height 42px height 42px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .body > .body
float left float left

View file

@ -3,7 +3,7 @@
*/ */
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex, { mapState } from 'vuex';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import VModal from 'vue-js-modal'; import VModal from 'vue-js-modal';
import * as TreeView from 'vue-json-tree-view'; import * as TreeView from 'vue-json-tree-view';
@ -41,17 +41,6 @@ require('./common/views/widgets');
// Register global filters // Register global filters
require('./common/views/filters'); require('./common/views/filters');
const store = new Vuex.Store({
state: {
uiHeaderHeight: 0
},
mutations: {
setUiHeaderHeight(state, height) {
state.uiHeaderHeight = height;
}
}
});
Vue.mixin({ Vue.mixin({
destroyed(this: any) { destroyed(this: any) {
if (this.$el.parentNode) { if (this.$el.parentNode) {
@ -159,20 +148,15 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API)
api: os.api, api: os.api,
apis: os.apis apis: os.apis
}; };
} },
computed: mapState({
clientSettings: state => state.settings.data
})
}); });
const app = new Vue({ const app = new Vue({
store, store: os.store,
router, router,
created() {
this.$watch('os.i', i => {
// キャッシュ更新
localStorage.setItem('me', JSON.stringify(i));
}, {
deep: true
});
},
render: createEl => createEl(App) render: createEl => createEl(App)
}); });

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="root sub"> <div class="root sub">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
@ -43,18 +41,13 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 12px 0 0 margin 0 12px 0 0
> .avatar
display block
width 48px width 48px
height 48px height 48px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .main > .main
float left float left

View file

@ -17,17 +17,12 @@
</div> </div>
<div class="renote" v-if="isRenote"> <div class="renote" v-if="isRenote">
<p> <p>
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>%fa:retweet%<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>がRenote
<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
</p> </p>
</div> </div>
<article> <article>
<header> <header>
<router-link class="avatar-anchor" :to="p.user | userPage"> <mk-avatar class="avatar" :user="p.user"/>
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div> <div>
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> <router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
<span class="username">@{{ p.user | acct }}</span> <span class="username">@{{ p.user | acct }}</span>
@ -152,7 +147,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { 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) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
@ -262,15 +257,10 @@ root(isDark)
margin 0 margin 0
padding 16px 32px padding 16px 32px
.avatar-anchor
display inline-block
.avatar .avatar
vertical-align bottom display inline-block
min-width 28px width 28px
min-height 28px height 28px
max-width 28px
max-height 28px
margin 0 8px 0 0 margin 0 8px 0 0
border-radius 6px border-radius 6px
@ -301,17 +291,12 @@ root(isDark)
display flex display flex
line-height 1.1em line-height 1.1em
> .avatar-anchor
display block
padding 0 12px 0 0
> .avatar > .avatar
display block display block
margin 0 12px 0 0
width 54px width 54px
height 54px height 54px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
@media (min-width 500px) @media (min-width 500px)
width 60px width 60px

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="mk-note-preview"> <div class="mk-note-preview">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
@ -37,18 +35,13 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 12px 0 0 margin 0 12px 0 0
> .avatar
display block
width 48px width 48px
height 48px height 48px
margin 0
border-radius 8px border-radius 8px
vertical-align bottom
> .main > .main
float left float left

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="sub"> <div class="sub">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
@ -49,23 +47,16 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 10px 0 0 margin 0 10px 0 0
width 44px
height 44px
border-radius 8px
@media (min-width 500px) @media (min-width 500px)
margin-right 16px margin-right 16px
> .avatar
display block
width 44px
height 44px
margin 0
border-radius 8px
vertical-align bottom
@media (min-width 500px)
width 52px width 52px
height 52px height 52px

View file

@ -1,12 +1,10 @@
<template> <template>
<div class="note" :class="{ renote: isRenote }"> <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"/> <x-sub :note="p.reply"/>
</div> </div>
<div class="renote" v-if="isRenote"> <div class="renote" v-if="isRenote">
<router-link class="avatar-anchor" :to="note.user | userPage"> <mk-avatar class="avatar" :user="note.user"/>
<img class="avatar" :src="`${note.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
%fa:retweet% %fa:retweet%
<span>{{ '%i18n:!@reposted-by%'.substr(0, '%i18n:!@reposted-by%'.indexOf('{')) }}</span> <span>{{ '%i18n:!@reposted-by%'.substr(0, '%i18n:!@reposted-by%'.indexOf('{')) }}</span>
<router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link> <router-link class="name" :to="note.user | userPage">{{ note.user | userName }}</router-link>
@ -14,9 +12,7 @@
<mk-time :time="note.createdAt"/> <mk-time :time="note.createdAt"/>
</div> </div>
<article> <article>
<router-link class="avatar-anchor" :to="p.user | userPage"> <mk-avatar class="avatar" :user="p.user"/>
<img class="avatar" :src="`${p.user.avatarUrl}?thumbnail&size=96`" alt="avatar"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link> <router-link class="name" :to="p.user | userPage">{{ p.user | userName }}</router-link>
@ -154,7 +150,7 @@ export default Vue.extend({
// Draw map // Draw map
if (this.p.geo) { 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) { if (shouldShowMap) {
(this as any).os.getGoogleMaps().then(maps => { (this as any).os.getGoogleMaps().then(maps => {
const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]); const uluru = new maps.LatLng(this.p.geo.coordinates[1], this.p.geo.coordinates[0]);
@ -268,11 +264,8 @@ root(isDark)
@media (min-width 600px) @media (min-width 600px)
padding 16px 32px padding 16px 32px
.avatar-anchor
display inline-block
.avatar .avatar
vertical-align bottom display inline-block
width 28px width 28px
height 28px height 28px
margin 0 8px 0 0 margin 0 8px 0 0
@ -314,26 +307,19 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 10px 8px 0 margin 0 10px 8px 0
width 48px
height 48px
border-radius 6px
//position -webkit-sticky //position -webkit-sticky
//position sticky //position sticky
//top 62px //top 62px
@media (min-width 500px) @media (min-width 500px)
margin-right 16px margin-right 16px
> .avatar
display block
width 48px
height 48px
margin 0
border-radius 6px
vertical-align bottom
@media (min-width 500px)
width 58px width 58px
height 58px height 58px
border-radius 8px border-radius 8px

View file

@ -116,13 +116,13 @@ export default Vue.extend({
const isMyNote = note.userId == (this as any).os.i.id; 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; 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) { if (isMyNote && isPureRenote) {
return; 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)) { if (isPureRenote && (note.renote.userId == (this as any).os.i.id)) {
return; return;
} }
@ -187,7 +187,7 @@ export default Vue.extend({
this.clearNotification(); this.clearNotification();
} }
if ((this as any).os.i.clientSettings.fetchOnScroll !== false) { if ((this as any).clientSettings.fetchOnScroll !== false) {
const current = window.scrollY + window.innerHeight; const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.loadMore(); if (current > document.body.offsetHeight - 8) this.loadMore();
} }

View file

@ -1,9 +1,7 @@
<template> <template>
<div class="mk-notification"> <div class="mk-notification">
<div class="notification reaction" v-if="notification.type == 'reaction'"> <div class="notification reaction" v-if="notification.type == 'reaction'">
<router-link class="avatar-anchor" :to="notification.user | userPage"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div> <div>
<header> <header>
<mk-reaction-icon :reaction="notification.reaction"/> <mk-reaction-icon :reaction="notification.reaction"/>
@ -18,9 +16,7 @@
</div> </div>
<div class="notification renote" v-if="notification.type == 'renote'"> <div class="notification renote" v-if="notification.type == 'renote'">
<router-link class="avatar-anchor" :to="notification.user | userPage"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div> <div>
<header> <header>
%fa:retweet% %fa:retweet%
@ -34,9 +30,7 @@
</div> </div>
<div class="notification follow" v-if="notification.type == 'follow'"> <div class="notification follow" v-if="notification.type == 'follow'">
<router-link class="avatar-anchor" :to="notification.user | userPage"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div> <div>
<header> <header>
%fa:user-plus% %fa:user-plus%
@ -47,9 +41,7 @@
</div> </div>
<div class="notification poll_vote" v-if="notification.type == 'poll_vote'"> <div class="notification poll_vote" v-if="notification.type == 'poll_vote'">
<router-link class="avatar-anchor" :to="notification.user | userPage"> <mk-avatar class="avatar" :user="notification.user"/>
<img class="avatar" :src="`${notification.user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div> <div>
<header> <header>
%fa:chart-pie% %fa:chart-pie%
@ -111,11 +103,9 @@ root(isDark)
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
img
width 36px width 36px
height 36px height 36px
border-radius 6px border-radius 6px

View file

@ -166,7 +166,7 @@ export default Vue.extend({
post() { post() {
this.posting = true; 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', { (this as any).api('notes/create', {
text: this.text == '' ? undefined : this.text, text: this.text == '' ? undefined : this.text,
mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, mediaIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,

View file

@ -46,8 +46,8 @@ export default Vue.extend({
(this as any).api('notes/user-list-timeline', { (this as any).api('notes/user-list-timeline', {
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@ -66,8 +66,8 @@ export default Vue.extend({
listId: this.list.id, listId: this.list.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="mk-user-preview"> <div class="mk-user-preview">
<router-link class="avatar-anchor" :to="user | userPage"> <mk-avatar class="avatar" :user="user"/>
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
<div class="main"> <div class="main">
<header> <header>
<router-link class="name" :to="user | userPage">{{ user | userName }}</router-link> <router-link class="name" :to="user | userPage">{{ user | userName }}</router-link>
@ -40,23 +38,16 @@ export default Vue.extend({
display block display block
clear both clear both
> .avatar-anchor > .avatar
display block display block
float left float left
margin 0 10px 0 0 margin 0 10px 0 0
width 48px
height 48px
border-radius 6px
@media (min-width 500px) @media (min-width 500px)
margin-right 16px margin-right 16px
> .avatar
display block
width 48px
height 48px
margin 0
border-radius 6px
vertical-align bottom
@media (min-width 500px)
width 58px width 58px
height 58px height 58px
border-radius 8px border-radius 8px

View file

@ -64,8 +64,8 @@ export default Vue.extend({
}; };
}, },
created() { created() {
if ((this as any).os.i.clientSettings.mobileHome == null) { if ((this as any).clientSettings.mobileHome == null) {
Vue.set((this as any).os.i.clientSettings, 'mobileHome', [{ Vue.set((this as any).clientSettings, 'mobileHome', [{
name: 'calendar', name: 'calendar',
id: 'a', data: {} id: 'a', data: {}
}, { }, {
@ -87,14 +87,14 @@ export default Vue.extend({
name: 'version', name: 'version',
id: 'g', data: {} id: 'g', data: {}
}]); }]);
this.widgets = (this as any).os.i.clientSettings.mobileHome; this.widgets = (this as any).clientSettings.mobileHome;
this.saveHome(); this.saveHome();
} else { } else {
this.widgets = (this as any).os.i.clientSettings.mobileHome; this.widgets = (this as any).clientSettings.mobileHome;
} }
this.$watch('os.i.clientSettings', i => { this.$watch('clientSettings', i => {
this.widgets = (this as any).os.i.clientSettings.mobileHome; this.widgets = (this as any).clientSettings.mobileHome;
}, { }, {
deep: true deep: true
}); });
@ -107,15 +107,15 @@ export default Vue.extend({
methods: { methods: {
onHomeUpdated(data) { onHomeUpdated(data) {
if (data.home) { if (data.home) {
(this as any).os.i.clientSettings.mobileHome = data.home; (this as any).clientSettings.mobileHome = data.home;
this.widgets = data.home; this.widgets = data.home;
} else { } 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) { if (w != null) {
w.data = data.data; w.data = data.data;
this.$refs[w.id][0].preventSave = true; this.$refs[w.id][0].preventSave = true;
this.$refs[w.id][0].props = w.data; 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(); this.saveHome();
}, },
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', { (this as any).api('i/update_mobile_home', {
home: this.widgets home: this.widgets
}); });

View file

@ -92,8 +92,8 @@ export default Vue.extend({
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, {
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilDate: this.date ? this.date.getTime() : undefined, untilDate: this.date ? this.date.getTime() : undefined,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
@ -114,8 +114,8 @@ export default Vue.extend({
(this as any).api(this.endpoint, { (this as any).api(this.endpoint, {
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: (this.$refs.timeline as any).tail().id, untilId: (this.$refs.timeline as any).tail().id,
includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, includeMyRenotes: (this as any).clientSettings.showMyRenotes,
includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes includeRenotedMyNotes: (this as any).clientSettings.showRenotedMyNotes
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();

View file

@ -22,9 +22,7 @@
<mk-welcome-timeline/> <mk-welcome-timeline/>
</div> </div>
<div class="users"> <div class="users">
<router-link v-for="user in users" :key="user.id" class="avatar-anchor" :to="`/@${user.username}`"> <mk-avatar class="avatar" :key="user.id" :user="user"/>
<img class="avatar" :src="`${user.avatarUrl}?thumbnail&size=64`" alt="avatar"/>
</router-link>
</div> </div>
<footer> <footer>
<small>{{ copyright }}</small> <small>{{ copyright }}</small>

View file

@ -21,6 +21,7 @@ export default define({
methods: { methods: {
func() { func() {
this.props.compact = !this.props.compact; this.props.compact = !this.props.compact;
this.save();
} }
} }
}); });

90
src/client/app/store.ts Normal file
View file

@ -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
});
}
}
}
}
});

View file

@ -24,16 +24,11 @@ module.exports = async (params, user) => new Promise(async (res, rej) => {
$set: x $set: x
}); });
// Serialize res();
user.clientSettings[name] = value;
const iObj = await pack(user, user, { // Publish event
detail: true, event(user._id, 'clientSettingUpdated', {
includeSecrets: true key: name,
value
}); });
// Send response
res(iObj);
// Publish i updated event
event(user._id, 'i_updated', iObj);
}); });

View file

@ -26,9 +26,9 @@ if (process.env.NODE_ENV != 'production') {
app.use(logger()); app.use(logger());
// Delay // Delay
app.use(slow({ //app.use(slow({
delay: 1000 // delay: 1000
})); //}));
} }
// Compress response // Compress response