wip
This commit is contained in:
		@@ -2,7 +2,7 @@ import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default function<T extends object>(data: {
 | 
			
		||||
	name: string;
 | 
			
		||||
	props?: T;
 | 
			
		||||
	props?: () => T;
 | 
			
		||||
}) {
 | 
			
		||||
	return Vue.extend({
 | 
			
		||||
		props: {
 | 
			
		||||
@@ -17,20 +17,9 @@ export default function<T extends object>(data: {
 | 
			
		||||
		},
 | 
			
		||||
		data() {
 | 
			
		||||
			return {
 | 
			
		||||
				props: data.props || {} as T
 | 
			
		||||
				props: data.props ? data.props() : {} as T
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
		watch: {
 | 
			
		||||
			props(newProps, oldProps) {
 | 
			
		||||
				if (JSON.stringify(newProps) == JSON.stringify(oldProps)) return;
 | 
			
		||||
				(this as any).api('i/update_home', {
 | 
			
		||||
					id: this.id,
 | 
			
		||||
					data: newProps
 | 
			
		||||
				}).then(() => {
 | 
			
		||||
					(this as any).os.i.client_settings.home.find(w => w.id == this.id).data = newProps;
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		created() {
 | 
			
		||||
			if (this.props) {
 | 
			
		||||
				Object.keys(this.props).forEach(prop => {
 | 
			
		||||
@@ -39,6 +28,18 @@ export default function<T extends object>(data: {
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.$watch('props', newProps => {
 | 
			
		||||
				console.log(this.id, newProps);
 | 
			
		||||
				(this as any).api('i/update_home', {
 | 
			
		||||
					id: this.id,
 | 
			
		||||
					data: newProps
 | 
			
		||||
				}).then(() => {
 | 
			
		||||
					(this as any).os.i.client_settings.home.find(w => w.id == this.id).data = newProps;
 | 
			
		||||
				});
 | 
			
		||||
			}, {
 | 
			
		||||
				deep: true
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import { EventEmitter } from 'eventemitter3';
 | 
			
		||||
import * as riot from 'riot';
 | 
			
		||||
import api from './scripts/api';
 | 
			
		||||
import signout from './scripts/signout';
 | 
			
		||||
import Progress from './scripts/loading';
 | 
			
		||||
import HomeStreamManager from './scripts/streaming/home-stream-manager';
 | 
			
		||||
import api from './scripts/api';
 | 
			
		||||
import DriveStreamManager from './scripts/streaming/drive-stream-manager';
 | 
			
		||||
import ServerStreamManager from './scripts/streaming/server-stream-manager';
 | 
			
		||||
import RequestsStreamManager from './scripts/streaming/requests-stream-manager';
 | 
			
		||||
@@ -226,22 +225,8 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
		// フェッチが完了したとき
 | 
			
		||||
		const fetched = me => {
 | 
			
		||||
			if (me) {
 | 
			
		||||
				riot.observable(me);
 | 
			
		||||
 | 
			
		||||
				// この me オブジェクトを更新するメソッド
 | 
			
		||||
				me.update = data => {
 | 
			
		||||
					if (data) Object.assign(me, data);
 | 
			
		||||
					me.trigger('updated');
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				// ローカルストレージにキャッシュ
 | 
			
		||||
				localStorage.setItem('me', JSON.stringify(me));
 | 
			
		||||
 | 
			
		||||
				// 自分の情報が更新されたとき
 | 
			
		||||
				me.on('updated', () => {
 | 
			
		||||
					// キャッシュ更新
 | 
			
		||||
					localStorage.setItem('me', JSON.stringify(me));
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.i = me;
 | 
			
		||||
@@ -270,8 +255,6 @@ export default class MiOS extends EventEmitter {
 | 
			
		||||
			// 後から新鮮なデータをフェッチ
 | 
			
		||||
			fetchme(cachedMe.token, freshData => {
 | 
			
		||||
				Object.assign(cachedMe, freshData);
 | 
			
		||||
				cachedMe.trigger('updated');
 | 
			
		||||
				cachedMe.trigger('refreshed');
 | 
			
		||||
			});
 | 
			
		||||
		} else {
 | 
			
		||||
			// Get token from cookie
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,9 @@ export default class Connection extends Stream {
 | 
			
		||||
		}, 1000 * 60);
 | 
			
		||||
 | 
			
		||||
		// 自分の情報が更新されたとき
 | 
			
		||||
		this.on('i_updated', me.update);
 | 
			
		||||
		this.on('i_updated', i => {
 | 
			
		||||
			Object.assign(me, i);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// トークンが再生成されたとき
 | 
			
		||||
		// このままではAPIが利用できないので強制的にサインアウトさせる
 | 
			
		||||
 
 | 
			
		||||
@@ -1,318 +0,0 @@
 | 
			
		||||
<mk-channel-home-widget>
 | 
			
		||||
	<template v-if="!data.compact">
 | 
			
		||||
		<p class="title">%fa:tv%{
 | 
			
		||||
			channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%'
 | 
			
		||||
		}</p>
 | 
			
		||||
		<button @click="settings" title="%i18n:desktop.tags.mk-channel-home-widget.settings%">%fa:cog%</button>
 | 
			
		||||
	</template>
 | 
			
		||||
	<p class="get-started" v-if="this.data.channel == null">%i18n:desktop.tags.mk-channel-home-widget.get-started%</p>
 | 
			
		||||
	<mk-channel ref="channel" show={ this.data.channel }/>
 | 
			
		||||
	<style lang="stylus" scoped>
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
			background #fff
 | 
			
		||||
			border solid 1px rgba(0, 0, 0, 0.075)
 | 
			
		||||
			border-radius 6px
 | 
			
		||||
			overflow hidden
 | 
			
		||||
 | 
			
		||||
			> .title
 | 
			
		||||
				z-index 2
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 0 16px
 | 
			
		||||
				line-height 42px
 | 
			
		||||
				font-size 0.9em
 | 
			
		||||
				font-weight bold
 | 
			
		||||
				color #888
 | 
			
		||||
				box-shadow 0 1px rgba(0, 0, 0, 0.07)
 | 
			
		||||
 | 
			
		||||
				> [data-fa]
 | 
			
		||||
					margin-right 4px
 | 
			
		||||
 | 
			
		||||
			> button
 | 
			
		||||
				position absolute
 | 
			
		||||
				z-index 2
 | 
			
		||||
				top 0
 | 
			
		||||
				right 0
 | 
			
		||||
				padding 0
 | 
			
		||||
				width 42px
 | 
			
		||||
				font-size 0.9em
 | 
			
		||||
				line-height 42px
 | 
			
		||||
				color #ccc
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
					color #aaa
 | 
			
		||||
 | 
			
		||||
				&:active
 | 
			
		||||
					color #999
 | 
			
		||||
 | 
			
		||||
			> .get-started
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 16px
 | 
			
		||||
				text-align center
 | 
			
		||||
				color #aaa
 | 
			
		||||
 | 
			
		||||
			> mk-channel
 | 
			
		||||
				height 200px
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script lang="typescript">
 | 
			
		||||
		this.data = {
 | 
			
		||||
			channel: null,
 | 
			
		||||
			compact: false
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.mixin('widget');
 | 
			
		||||
 | 
			
		||||
		this.on('mount', () => {
 | 
			
		||||
			if (this.data.channel) {
 | 
			
		||||
				this.zap();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.zap = () => {
 | 
			
		||||
			this.update({
 | 
			
		||||
				fetching: true
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.$root.$data.os.api('channels/show', {
 | 
			
		||||
				channel_id: this.data.channel
 | 
			
		||||
			}).then(channel => {
 | 
			
		||||
				this.update({
 | 
			
		||||
					fetching: false,
 | 
			
		||||
					channel: channel
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				this.$refs.channel.zap(channel);
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.settings = () => {
 | 
			
		||||
			const id = window.prompt('チャンネルID');
 | 
			
		||||
			if (!id) return;
 | 
			
		||||
			this.data.channel = id;
 | 
			
		||||
			this.zap();
 | 
			
		||||
 | 
			
		||||
			// Save state
 | 
			
		||||
			this.save();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.func = () => {
 | 
			
		||||
			this.data.compact = !this.data.compact;
 | 
			
		||||
			this.save();
 | 
			
		||||
		};
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-channel-home-widget>
 | 
			
		||||
 | 
			
		||||
<mk-channel>
 | 
			
		||||
	<p v-if="fetching">読み込み中<mk-ellipsis/></p>
 | 
			
		||||
	<div v-if="!fetching" ref="posts">
 | 
			
		||||
		<p v-if="posts.length == 0">まだ投稿がありません</p>
 | 
			
		||||
		<mk-channel-post each={ post in posts.slice().reverse() } post={ post } form={ parent.refs.form }/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<mk-channel-form ref="form"/>
 | 
			
		||||
	<style lang="stylus" scoped>
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
 | 
			
		||||
			> p
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 16px
 | 
			
		||||
				text-align center
 | 
			
		||||
				color #aaa
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				height calc(100% - 38px)
 | 
			
		||||
				overflow auto
 | 
			
		||||
				font-size 0.9em
 | 
			
		||||
 | 
			
		||||
				> mk-channel-post
 | 
			
		||||
					border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
					&:last-child
 | 
			
		||||
						border-bottom none
 | 
			
		||||
 | 
			
		||||
			> mk-channel-form
 | 
			
		||||
				position absolute
 | 
			
		||||
				left 0
 | 
			
		||||
				bottom 0
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script lang="typescript">
 | 
			
		||||
		import ChannelStream from '../../../common/scripts/streaming/channel-stream';
 | 
			
		||||
 | 
			
		||||
		this.mixin('api');
 | 
			
		||||
 | 
			
		||||
		this.fetching = true;
 | 
			
		||||
		this.channel = null;
 | 
			
		||||
		this.posts = [];
 | 
			
		||||
 | 
			
		||||
		this.on('unmount', () => {
 | 
			
		||||
			if (this.connection) {
 | 
			
		||||
				this.connection.off('post', this.onPost);
 | 
			
		||||
				this.connection.close();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.zap = channel => {
 | 
			
		||||
			this.update({
 | 
			
		||||
				fetching: true,
 | 
			
		||||
				channel: channel
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.$root.$data.os.api('channels/posts', {
 | 
			
		||||
				channel_id: channel.id
 | 
			
		||||
			}).then(posts => {
 | 
			
		||||
				this.update({
 | 
			
		||||
					fetching: false,
 | 
			
		||||
					posts: posts
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				this.scrollToBottom();
 | 
			
		||||
 | 
			
		||||
				if (this.connection) {
 | 
			
		||||
					this.connection.off('post', this.onPost);
 | 
			
		||||
					this.connection.close();
 | 
			
		||||
				}
 | 
			
		||||
				this.connection = new ChannelStream(this.channel.id);
 | 
			
		||||
				this.connection.on('post', this.onPost);
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.onPost = post => {
 | 
			
		||||
			this.posts.unshift(post);
 | 
			
		||||
			this.update();
 | 
			
		||||
			this.scrollToBottom();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.scrollToBottom = () => {
 | 
			
		||||
			this.$refs.posts.scrollTop = this.$refs.posts.scrollHeight;
 | 
			
		||||
		};
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-channel>
 | 
			
		||||
 | 
			
		||||
<mk-channel-post>
 | 
			
		||||
	<header>
 | 
			
		||||
		<a class="index" @click="reply">{ post.index }:</a>
 | 
			
		||||
		<a class="name" href={ _URL_ + '/' + post.user.username }><b>{ post.user.name }</b></a>
 | 
			
		||||
		<span>ID:<i>{ post.user.username }</i></span>
 | 
			
		||||
	</header>
 | 
			
		||||
	<div>
 | 
			
		||||
		<a v-if="post.reply">>>{ post.reply.index }</a>
 | 
			
		||||
		{ post.text }
 | 
			
		||||
		<div class="media" v-if="post.media">
 | 
			
		||||
			<template each={ file in post.media }>
 | 
			
		||||
				<a href={ file.url } target="_blank">
 | 
			
		||||
					<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/>
 | 
			
		||||
				</a>
 | 
			
		||||
			</template>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<style lang="stylus" scoped>
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
			margin 0
 | 
			
		||||
			padding 0
 | 
			
		||||
			color #444
 | 
			
		||||
 | 
			
		||||
			> header
 | 
			
		||||
				position -webkit-sticky
 | 
			
		||||
				position sticky
 | 
			
		||||
				z-index 1
 | 
			
		||||
				top 0
 | 
			
		||||
				padding 8px 4px 4px 16px
 | 
			
		||||
				background rgba(255, 255, 255, 0.9)
 | 
			
		||||
 | 
			
		||||
				> .index
 | 
			
		||||
					margin-right 0.25em
 | 
			
		||||
 | 
			
		||||
				> .name
 | 
			
		||||
					margin-right 0.5em
 | 
			
		||||
					color #008000
 | 
			
		||||
 | 
			
		||||
			> div
 | 
			
		||||
				padding 0 16px 16px 16px
 | 
			
		||||
 | 
			
		||||
				> .media
 | 
			
		||||
					> a
 | 
			
		||||
						display inline-block
 | 
			
		||||
 | 
			
		||||
						> img
 | 
			
		||||
							max-width 100%
 | 
			
		||||
							vertical-align bottom
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script lang="typescript">
 | 
			
		||||
		this.post = this.opts.post;
 | 
			
		||||
		this.form = this.opts.form;
 | 
			
		||||
 | 
			
		||||
		this.reply = () => {
 | 
			
		||||
			this.form.refs.text.value = `>>${ this.post.index } `;
 | 
			
		||||
		};
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-channel-post>
 | 
			
		||||
 | 
			
		||||
<mk-channel-form>
 | 
			
		||||
	<input ref="text" disabled={ wait } onkeydown={ onkeydown } placeholder="書いて">
 | 
			
		||||
	<style lang="stylus" scoped>
 | 
			
		||||
		:scope
 | 
			
		||||
			display block
 | 
			
		||||
			width 100%
 | 
			
		||||
			height 38px
 | 
			
		||||
			padding 4px
 | 
			
		||||
			border-top solid 1px #ddd
 | 
			
		||||
 | 
			
		||||
			> input
 | 
			
		||||
				padding 0 8px
 | 
			
		||||
				width 100%
 | 
			
		||||
				height 100%
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				color #55595c
 | 
			
		||||
				border solid 1px #dadada
 | 
			
		||||
				border-radius 4px
 | 
			
		||||
 | 
			
		||||
				&:hover
 | 
			
		||||
				&:focus
 | 
			
		||||
					border-color #aeaeae
 | 
			
		||||
 | 
			
		||||
	</style>
 | 
			
		||||
	<script lang="typescript">
 | 
			
		||||
		this.mixin('api');
 | 
			
		||||
 | 
			
		||||
		this.clear = () => {
 | 
			
		||||
			this.$refs.text.value = '';
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.onkeydown = e => {
 | 
			
		||||
			if (e.which == 10 || e.which == 13) this.post();
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		this.post = () => {
 | 
			
		||||
			this.update({
 | 
			
		||||
				wait: true
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			let text = this.$refs.text.value;
 | 
			
		||||
			let reply = null;
 | 
			
		||||
 | 
			
		||||
			if (/^>>([0-9]+) /.test(text)) {
 | 
			
		||||
				const index = text.match(/^>>([0-9]+) /)[1];
 | 
			
		||||
				reply = this.parent.posts.find(p => p.index.toString() == index);
 | 
			
		||||
				text = text.replace(/^>>([0-9]+) /, '');
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.$root.$data.os.api('posts/create', {
 | 
			
		||||
				text: text,
 | 
			
		||||
				reply_id: reply ? reply.id : undefined,
 | 
			
		||||
				channel_id: this.parent.channel.id
 | 
			
		||||
			}).then(data => {
 | 
			
		||||
				this.clear();
 | 
			
		||||
			}).catch(err => {
 | 
			
		||||
				alert('失敗した');
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				this.update({
 | 
			
		||||
					wait: false
 | 
			
		||||
				});
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
	</script>
 | 
			
		||||
</mk-channel-form>
 | 
			
		||||
@@ -23,6 +23,7 @@ import MkIndex from './views/pages/index.vue';
 | 
			
		||||
import MkUser from './views/pages/user/user.vue';
 | 
			
		||||
import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
			
		||||
import MkDrive from './views/pages/drive.vue';
 | 
			
		||||
import MkHomeCustomize from './views/pages/home-customize.vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * init
 | 
			
		||||
@@ -66,6 +67,8 @@ init(async (launch) => {
 | 
			
		||||
 | 
			
		||||
	app.$router.addRoutes([{
 | 
			
		||||
		path: '/', name: 'index', component: MkIndex
 | 
			
		||||
	}, {
 | 
			
		||||
		path: '/i/customize-home', component: MkHomeCustomize
 | 
			
		||||
	}, {
 | 
			
		||||
		path: '/i/drive', component: MkDrive
 | 
			
		||||
	}, {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mk-calendar">
 | 
			
		||||
<div class="mk-calendar" :data-melt="design == 4 || design == 5">
 | 
			
		||||
	<template v-if="design == 0 || design == 1">
 | 
			
		||||
		<button @click="prev" title="%i18n:desktop.tags.mk-calendar-widget.prev%">%fa:chevron-circle-left%</button>
 | 
			
		||||
		<p class="title">{{ '%i18n:desktop.tags.mk-calendar-widget.title%'.replace('{1}', year).replace('{2}', month) }}</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@
 | 
			
		||||
		<div v-for="place in ['left', 'main', 'right']" :class="place" :ref="place" :data-place="place">
 | 
			
		||||
			<template v-if="place != 'main'">
 | 
			
		||||
				<template v-for="widget in widgets[place]">
 | 
			
		||||
					<div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
 | 
			
		||||
					<div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)" :data-widget-id="widget.id">
 | 
			
		||||
						<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id"/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<template v-else>
 | 
			
		||||
@@ -60,7 +60,7 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import Sortable from 'sortablejs';
 | 
			
		||||
import * as Sortable from 'sortablejs';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
@@ -72,7 +72,6 @@ export default Vue.extend({
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			home: [],
 | 
			
		||||
			bakedHomeData: null,
 | 
			
		||||
			widgetAdderSelected: null
 | 
			
		||||
		};
 | 
			
		||||
@@ -95,16 +94,15 @@ export default Vue.extend({
 | 
			
		||||
		},
 | 
			
		||||
		rightEl(): Element {
 | 
			
		||||
			return (this.$refs.right as Element[])[0];
 | 
			
		||||
		},
 | 
			
		||||
		home(): any {
 | 
			
		||||
			return (this as any).os.i.client_settings.home;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		this.bakedHomeData = this.bakeHomeData();
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		(this as any).os.i.on('refreshed', this.onMeRefreshed);
 | 
			
		||||
 | 
			
		||||
		this.home = (this as any).os.i.client_settings.home;
 | 
			
		||||
 | 
			
		||||
		this.$nextTick(() => {
 | 
			
		||||
			if (!this.customize) {
 | 
			
		||||
				if (this.leftEl.children.length == 0) {
 | 
			
		||||
@@ -132,7 +130,7 @@ export default Vue.extend({
 | 
			
		||||
					animation: 150,
 | 
			
		||||
					onMove: evt => {
 | 
			
		||||
						const id = evt.dragged.getAttribute('data-widget-id');
 | 
			
		||||
						this.home.find(tag => tag.id == id).widget.place = evt.to.getAttribute('data-place');
 | 
			
		||||
						this.home.find(w => w.id == id).place = evt.to.getAttribute('data-place');
 | 
			
		||||
					},
 | 
			
		||||
					onSort: () => {
 | 
			
		||||
						this.saveHome();
 | 
			
		||||
@@ -153,24 +151,15 @@ export default Vue.extend({
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		(this as any).os.i.off('refreshed', this.onMeRefreshed);
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		bakeHomeData() {
 | 
			
		||||
			return JSON.stringify((this as any).os.i.client_settings.home);
 | 
			
		||||
			return JSON.stringify(this.home);
 | 
			
		||||
		},
 | 
			
		||||
		onTlLoaded() {
 | 
			
		||||
			this.$emit('loaded');
 | 
			
		||||
		},
 | 
			
		||||
		onMeRefreshed() {
 | 
			
		||||
			if (this.bakedHomeData != this.bakeHomeData()) {
 | 
			
		||||
				// TODO: i18n
 | 
			
		||||
				alert('別の場所でホームが編集されました。ページを再度読み込みすると編集が反映されます。');
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onWidgetContextmenu(widgetId) {
 | 
			
		||||
			(this.$refs[widgetId] as any).func();
 | 
			
		||||
			(this.$refs[widgetId] as any)[0].func();
 | 
			
		||||
		},
 | 
			
		||||
		addWidget() {
 | 
			
		||||
			const widget = {
 | 
			
		||||
@@ -180,29 +169,13 @@ export default Vue.extend({
 | 
			
		||||
				data: {}
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			(this as any).os.i.client_settings.home.unshift(widget);
 | 
			
		||||
			this.home.unshift(widget);
 | 
			
		||||
 | 
			
		||||
			this.saveHome();
 | 
			
		||||
		},
 | 
			
		||||
		saveHome() {
 | 
			
		||||
			const data = [];
 | 
			
		||||
 | 
			
		||||
			Array.from(this.leftEl.children).forEach(el => {
 | 
			
		||||
				const id = el.getAttribute('data-widget-id');
 | 
			
		||||
				const widget = (this as any).os.i.client_settings.home.find(w => w.id == id);
 | 
			
		||||
				widget.place = 'left';
 | 
			
		||||
				data.push(widget);
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			Array.from(this.rightEl.children).forEach(el => {
 | 
			
		||||
				const id = el.getAttribute('data-widget-id');
 | 
			
		||||
				const widget = (this as any).os.i.client_settings.home.find(w => w.id == id);
 | 
			
		||||
				widget.place = 'right';
 | 
			
		||||
				data.push(widget);
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			(this as any).api('i/update_home', {
 | 
			
		||||
				home: data
 | 
			
		||||
				home: this.home
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		warp(date) {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import wDonation from './widgets/donation.vue';
 | 
			
		||||
import wNotifications from './widgets/notifications.vue';
 | 
			
		||||
import wBroadcast from './widgets/broadcast.vue';
 | 
			
		||||
import wTimemachine from './widgets/timemachine.vue';
 | 
			
		||||
import wProfile from './widgets/profile.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mk-ui', ui);
 | 
			
		||||
Vue.component('mk-ui-notification', uiNotification);
 | 
			
		||||
@@ -71,3 +72,4 @@ Vue.component('mkw-donation', wDonation);
 | 
			
		||||
Vue.component('mkw-notifications', wNotifications);
 | 
			
		||||
Vue.component('mkw-broadcast', wBroadcast);
 | 
			
		||||
Vue.component('mkw-timemachine', wTimemachine);
 | 
			
		||||
Vue.component('mkw-profile', wProfile);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,10 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'activity',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0,
 | 
			
		||||
		view: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,9 @@ import { lang } from '../../../../config';
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'broadcast',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,9 +38,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'calendar',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="form">
 | 
			
		||||
	<input v-model="text" :disabled="wait" @keydown="onKeydown" placeholder="書いて">
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			text: '',
 | 
			
		||||
			wait: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		onKeydown(e) {
 | 
			
		||||
			if (e.which == 10 || e.which == 13) this.post();
 | 
			
		||||
		},
 | 
			
		||||
		post() {
 | 
			
		||||
			this.wait = true;
 | 
			
		||||
 | 
			
		||||
			let reply = null;
 | 
			
		||||
 | 
			
		||||
			if (/^>>([0-9]+) /.test(this.text)) {
 | 
			
		||||
				const index = this.text.match(/^>>([0-9]+) /)[1];
 | 
			
		||||
				reply = (this.$parent as any).posts.find(p => p.index.toString() == index);
 | 
			
		||||
				this.text = this.text.replace(/^>>([0-9]+) /, '');
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			(this as any).api('posts/create', {
 | 
			
		||||
				text: this.text,
 | 
			
		||||
				reply_id: reply ? reply.id : undefined,
 | 
			
		||||
				channel_id: (this.$parent as any).channel.id
 | 
			
		||||
			}).then(data => {
 | 
			
		||||
				this.text = '';
 | 
			
		||||
			}).catch(err => {
 | 
			
		||||
				alert('失敗した');
 | 
			
		||||
			}).then(() => {
 | 
			
		||||
				this.wait = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.form
 | 
			
		||||
	width 100%
 | 
			
		||||
	height 38px
 | 
			
		||||
	padding 4px
 | 
			
		||||
	border-top solid 1px #ddd
 | 
			
		||||
 | 
			
		||||
	> input
 | 
			
		||||
		padding 0 8px
 | 
			
		||||
		width 100%
 | 
			
		||||
		height 100%
 | 
			
		||||
		font-size 14px
 | 
			
		||||
		color #55595c
 | 
			
		||||
		border solid 1px #dadada
 | 
			
		||||
		border-radius 4px
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
		&:focus
 | 
			
		||||
			border-color #aeaeae
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="post">
 | 
			
		||||
	<header>
 | 
			
		||||
		<a class="index" @click="reply">{{ post.index }}:</a>
 | 
			
		||||
		<router-link class="name" :to="`/${post.user.username}`" v-user-preview="post.user.id"><b>{{ post.user.name }}</b></router-link>
 | 
			
		||||
		<span>ID:<i>{{ post.user.username }}</i></span>
 | 
			
		||||
	</header>
 | 
			
		||||
	<div>
 | 
			
		||||
		<a v-if="post.reply">>>{{ post.reply.index }}</a>
 | 
			
		||||
		{{ post.text }}
 | 
			
		||||
		<div class="media" v-if="post.media">
 | 
			
		||||
			<a v-for="file in post.media" :href="file.url" target="_blank">
 | 
			
		||||
				<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/>
 | 
			
		||||
			</a>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: ['post'],
 | 
			
		||||
	methods: {
 | 
			
		||||
		reply() {
 | 
			
		||||
			this.$emit('reply', this.post);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.post
 | 
			
		||||
	margin 0
 | 
			
		||||
	padding 0
 | 
			
		||||
	color #444
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		position -webkit-sticky
 | 
			
		||||
		position sticky
 | 
			
		||||
		z-index 1
 | 
			
		||||
		top 0
 | 
			
		||||
		padding 8px 4px 4px 16px
 | 
			
		||||
		background rgba(255, 255, 255, 0.9)
 | 
			
		||||
 | 
			
		||||
		> .index
 | 
			
		||||
			margin-right 0.25em
 | 
			
		||||
 | 
			
		||||
		> .name
 | 
			
		||||
			margin-right 0.5em
 | 
			
		||||
			color #008000
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		padding 0 16px 16px 16px
 | 
			
		||||
 | 
			
		||||
		> .media
 | 
			
		||||
			> a
 | 
			
		||||
				display inline-block
 | 
			
		||||
 | 
			
		||||
				> img
 | 
			
		||||
					max-width 100%
 | 
			
		||||
					vertical-align bottom
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										104
									
								
								src/web/app/desktop/views/components/widgets/channel.channel.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/web/app/desktop/views/components/widgets/channel.channel.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="channel">
 | 
			
		||||
	<p v-if="fetching">読み込み中<mk-ellipsis/></p>
 | 
			
		||||
	<div v-if="!fetching" ref="posts">
 | 
			
		||||
		<p v-if="posts.length == 0">まだ投稿がありません</p>
 | 
			
		||||
		<x-post class="post" v-for="post in posts.slice().reverse()" :post="post" :key="post.id" @reply="reply"/>
 | 
			
		||||
	</div>
 | 
			
		||||
	<x-form class="form" ref="form"/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import ChannelStream from '../../../../common/scripts/streaming/channel-stream';
 | 
			
		||||
import XForm from './channel.channel.form.vue';
 | 
			
		||||
import XPost from './channel.channel.post.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XForm,
 | 
			
		||||
		XPost
 | 
			
		||||
	},
 | 
			
		||||
	props: ['channel'],
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			posts: [],
 | 
			
		||||
			connection: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	watch: {
 | 
			
		||||
		channel() {
 | 
			
		||||
			this.zap();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.zap();
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.disconnect();
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		zap() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this as any).api('channels/posts', {
 | 
			
		||||
				channel_id: this.channel.id
 | 
			
		||||
			}).then(posts => {
 | 
			
		||||
				this.posts = posts;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
 | 
			
		||||
				this.scrollToBottom();
 | 
			
		||||
 | 
			
		||||
				this.disconnect();
 | 
			
		||||
				this.connection = new ChannelStream(this.channel.id);
 | 
			
		||||
				this.connection.on('post', this.onPost);
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		disconnect() {
 | 
			
		||||
			if (this.connection) {
 | 
			
		||||
				this.connection.off('post', this.onPost);
 | 
			
		||||
				this.connection.close();
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onPost(post) {
 | 
			
		||||
			this.posts.unshift(post);
 | 
			
		||||
			this.scrollToBottom();
 | 
			
		||||
		},
 | 
			
		||||
		scrollToBottom() {
 | 
			
		||||
			(this.$refs.posts as any).scrollTop = (this.$refs.posts as any).scrollHeight;
 | 
			
		||||
		},
 | 
			
		||||
		reply(post) {
 | 
			
		||||
			(this.$refs.form as any).text = `>>${ post.index } `;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.channel
 | 
			
		||||
 | 
			
		||||
	> p
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
 | 
			
		||||
	> div
 | 
			
		||||
		height calc(100% - 38px)
 | 
			
		||||
		overflow auto
 | 
			
		||||
		font-size 0.9em
 | 
			
		||||
 | 
			
		||||
		> .post
 | 
			
		||||
			border-bottom solid 1px #eee
 | 
			
		||||
 | 
			
		||||
			&:last-child
 | 
			
		||||
				border-bottom none
 | 
			
		||||
 | 
			
		||||
	> .form
 | 
			
		||||
		position absolute
 | 
			
		||||
		left 0
 | 
			
		||||
		bottom 0
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										107
									
								
								src/web/app/desktop/views/components/widgets/channel.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/web/app/desktop/views/components/widgets/channel.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
<template>
 | 
			
		||||
<div class="mkw-channel">
 | 
			
		||||
	<template v-if="!data.compact">
 | 
			
		||||
		<p class="title">%fa:tv%{{ channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%' }}</p>
 | 
			
		||||
		<button @click="settings" title="%i18n:desktop.tags.mk-channel-home-widget.settings%">%fa:cog%</button>
 | 
			
		||||
	</template>
 | 
			
		||||
	<p class="get-started" v-if="props.channel == null">%i18n:desktop.tags.mk-channel-home-widget.get-started%</p>
 | 
			
		||||
	<x-channel class="channel" :channel="channel" v-else/>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
import XChannel from './channel.channel.vue';
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'server',
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		channel: null,
 | 
			
		||||
		compact: false
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XChannel
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			channel: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		if (this.props.channel) {
 | 
			
		||||
				this.zap();
 | 
			
		||||
			}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		func() {
 | 
			
		||||
			this.props.compact = !this.props.compact;
 | 
			
		||||
		},
 | 
			
		||||
		settings() {
 | 
			
		||||
			const id = window.prompt('チャンネルID');
 | 
			
		||||
			if (!id) return;
 | 
			
		||||
			this.props.channel = id;
 | 
			
		||||
			this.zap();
 | 
			
		||||
		},
 | 
			
		||||
		zap() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this as any).api('channels/show', {
 | 
			
		||||
				channel_id: this.props.channel
 | 
			
		||||
			}).then(channel => {
 | 
			
		||||
				this.channel = channel;
 | 
			
		||||
				this.fetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.mkw-channel
 | 
			
		||||
	background #fff
 | 
			
		||||
	border solid 1px rgba(0, 0, 0, 0.075)
 | 
			
		||||
	border-radius 6px
 | 
			
		||||
	overflow hidden
 | 
			
		||||
 | 
			
		||||
	> .title
 | 
			
		||||
		z-index 2
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 0 16px
 | 
			
		||||
		line-height 42px
 | 
			
		||||
		font-size 0.9em
 | 
			
		||||
		font-weight bold
 | 
			
		||||
		color #888
 | 
			
		||||
		box-shadow 0 1px rgba(0, 0, 0, 0.07)
 | 
			
		||||
 | 
			
		||||
		> [data-fa]
 | 
			
		||||
			margin-right 4px
 | 
			
		||||
 | 
			
		||||
	> button
 | 
			
		||||
		position absolute
 | 
			
		||||
		z-index 2
 | 
			
		||||
		top 0
 | 
			
		||||
		right 0
 | 
			
		||||
		padding 0
 | 
			
		||||
		width 42px
 | 
			
		||||
		font-size 0.9em
 | 
			
		||||
		line-height 42px
 | 
			
		||||
		color #ccc
 | 
			
		||||
 | 
			
		||||
		&:hover
 | 
			
		||||
			color #aaa
 | 
			
		||||
 | 
			
		||||
		&:active
 | 
			
		||||
			color #999
 | 
			
		||||
 | 
			
		||||
	> .get-started
 | 
			
		||||
		margin 0
 | 
			
		||||
		padding 16px
 | 
			
		||||
		text-align center
 | 
			
		||||
		color #aaa
 | 
			
		||||
 | 
			
		||||
	> .channel
 | 
			
		||||
		height 200px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -9,9 +9,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'messaging',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		navigate(user) {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'notifications',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		compact: false
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		settings() {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'photo-stream',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'polls',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		compact: false
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'post-form',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,19 +4,19 @@
 | 
			
		||||
	:data-melt="props.design == 2"
 | 
			
		||||
>
 | 
			
		||||
	<div class="banner"
 | 
			
		||||
		style={ I.banner_url ? 'background-image: url(' + I.banner_url + '?thumbnail&size=256)' : '' }
 | 
			
		||||
		:style="os.i.banner_url ? `background-image: url(${os.i.banner_url}?thumbnail&size=256)` : ''"
 | 
			
		||||
		title="クリックでバナー編集"
 | 
			
		||||
		@click="wapi_setBanner"
 | 
			
		||||
		@click="os.apis.updateBanner"
 | 
			
		||||
	></div>
 | 
			
		||||
	<img class="avatar"
 | 
			
		||||
		src={ I.avatar_url + '?thumbnail&size=96' }
 | 
			
		||||
		@click="wapi_setAvatar"
 | 
			
		||||
		:src="`${os.i.avatar_url}?thumbnail&size=96`"
 | 
			
		||||
		@click="os.apis.updateAvatar"
 | 
			
		||||
		alt="avatar"
 | 
			
		||||
		title="クリックでアバター編集"
 | 
			
		||||
		v-user-preview={ I.id }
 | 
			
		||||
		v-user-preview="os.i.id"
 | 
			
		||||
	/>
 | 
			
		||||
	<a class="name" href={ '/' + I.username }>{ I.name }</a>
 | 
			
		||||
	<p class="username">@{ I.username }</p>
 | 
			
		||||
	<router-link class="name" :to="`/${os.i.username}`">{{ os.i.name }}</router-link>
 | 
			
		||||
	<p class="username">@{{ os.i.username }}</p>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -24,9 +24,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'profile',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,9 +15,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'rss',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		compact: false
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,10 @@ import XInfo from './server.info.vue';
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'server',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0,
 | 
			
		||||
		view: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XCpuMemory,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,10 @@ import * as anime from 'animejs';
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'slideshow',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		folder: undefined,
 | 
			
		||||
		size: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'timemachine',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		design: 0
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	methods: {
 | 
			
		||||
		chosen(date) {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,9 @@
 | 
			
		||||
import define from '../../../../common/define-widget';
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'trends',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		compact: false
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,9 +28,9 @@ const limit = 3;
 | 
			
		||||
 | 
			
		||||
export default define({
 | 
			
		||||
	name: 'users',
 | 
			
		||||
	props: {
 | 
			
		||||
	props: () => ({
 | 
			
		||||
		compact: false
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
}).extend({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
	<mk-home customize/>
 | 
			
		||||
<mk-home customize/>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -103,6 +103,14 @@ export default (callback: (launch: (api: (os: MiOS) => API) => [Vue, MiOS]) => v
 | 
			
		||||
				router: new VueRouter({
 | 
			
		||||
					mode: 'history'
 | 
			
		||||
				}),
 | 
			
		||||
				created() {
 | 
			
		||||
					this.$watch('os.i', i => {
 | 
			
		||||
						// キャッシュ更新
 | 
			
		||||
						localStorage.setItem('me', JSON.stringify(i));
 | 
			
		||||
					}, {
 | 
			
		||||
						deep: true
 | 
			
		||||
					});
 | 
			
		||||
				},
 | 
			
		||||
				render: createEl => createEl(App)
 | 
			
		||||
			}).$mount('#app');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user