Implement #1098
This commit is contained in:
		
							parent
							
								
									83f2e906bb
								
							
						
					
					
						commit
						df8a2aea35
					
				|  | @ -182,7 +182,12 @@ const endpoints: Endpoint[] = [ | |||
| 	{ | ||||
| 		name: 'i/update_home', | ||||
| 		withCredential: true, | ||||
| 		kind: 'account-write' | ||||
| 		secure: true | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'i/update_mobile_home', | ||||
| 		withCredential: true, | ||||
| 		secure: true | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'i/change_password', | ||||
|  |  | |||
|  | @ -4,16 +4,7 @@ | |||
| import $ from 'cafy'; | ||||
| import User from '../../models/user'; | ||||
| 
 | ||||
| /** | ||||
|  * Update myself | ||||
|  * | ||||
|  * @param {any} params | ||||
|  * @param {any} user | ||||
|  * @param {any} _ | ||||
|  * @param {boolean} isSecure | ||||
|  * @return {Promise<any>} | ||||
|  */ | ||||
| module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => { | ||||
| module.exports = async (params, user) => new Promise(async (res, rej) => { | ||||
| 	// Get 'home' parameter
 | ||||
| 	const [home, homeErr] = $(params.home).optional.array().each( | ||||
| 		$().strict.object() | ||||
|  |  | |||
|  | @ -0,0 +1,50 @@ | |||
| /** | ||||
|  * Module dependencies | ||||
|  */ | ||||
| import $ from 'cafy'; | ||||
| import User from '../../models/user'; | ||||
| 
 | ||||
| module.exports = async (params, user) => new Promise(async (res, rej) => { | ||||
| 	// Get 'home' parameter
 | ||||
| 	const [home, homeErr] = $(params.home).optional.array().each( | ||||
| 		$().strict.object() | ||||
| 			.have('name', $().string()) | ||||
| 			.have('id', $().string()) | ||||
| 			.have('data', $().object())).$; | ||||
| 	if (homeErr) return rej('invalid home param'); | ||||
| 
 | ||||
| 	// Get 'id' parameter
 | ||||
| 	const [id, idErr] = $(params.id).optional.string().$; | ||||
| 	if (idErr) return rej('invalid id param'); | ||||
| 
 | ||||
| 	// Get 'data' parameter
 | ||||
| 	const [data, dataErr] = $(params.data).optional.object().$; | ||||
| 	if (dataErr) return rej('invalid data param'); | ||||
| 
 | ||||
| 	if (home) { | ||||
| 		await User.update(user._id, { | ||||
| 			$set: { | ||||
| 				'client_settings.mobile_home': home | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		res(); | ||||
| 	} else { | ||||
| 		if (id == null && data == null) return rej('you need to set id and data params if home param unset'); | ||||
| 
 | ||||
| 		const _home = user.client_settings.mobile_home || []; | ||||
| 		const widget = _home.find(w => w.id == id); | ||||
| 
 | ||||
| 		if (widget == null) return rej('widget not found'); | ||||
| 
 | ||||
| 		widget.data = data; | ||||
| 
 | ||||
| 		await User.update(user._id, { | ||||
| 			$set: { | ||||
| 				'client_settings.mobile_home': _home | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		res(); | ||||
| 	} | ||||
| }); | ||||
|  | @ -8,6 +8,10 @@ export default function<T extends object>(data: { | |||
| 		props: { | ||||
| 			widget: { | ||||
| 				type: Object | ||||
| 			}, | ||||
| 			isMobile: { | ||||
| 				type: Boolean, | ||||
| 				default: false | ||||
| 			} | ||||
| 		}, | ||||
| 		computed: { | ||||
|  | @ -21,6 +25,7 @@ export default function<T extends object>(data: { | |||
| 			}; | ||||
| 		}, | ||||
| 		created() { | ||||
| 			if (this.widget.data == null) this.widget.data = {}; | ||||
| 			if (this.props) { | ||||
| 				Object.keys(this.props).forEach(prop => { | ||||
| 					if (this.widget.data.hasOwnProperty(prop)) { | ||||
|  | @ -30,12 +35,21 @@ export default function<T extends object>(data: { | |||
| 			} | ||||
| 
 | ||||
| 			this.$watch('props', 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; | ||||
| 				}); | ||||
| 				if (this.isMobile) { | ||||
| 					(this as any).api('i/update_mobile_home', { | ||||
| 						id: this.id, | ||||
| 						data: newProps | ||||
| 					}).then(() => { | ||||
| 						(this as any).os.i.client_settings.mobile_home.find(w => w.id == this.id).data = newProps; | ||||
| 					}); | ||||
| 				} else { | ||||
| 					(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 | ||||
| 			}); | ||||
|  |  | |||
|  | @ -9,7 +9,9 @@ export default async function(mios: MiOS) { | |||
| 
 | ||||
| 		// Clear cache (serive worker)
 | ||||
| 		try { | ||||
| 			navigator.serviceWorker.controller.postMessage('clear'); | ||||
| 			if (navigator.serviceWorker.controller) { | ||||
| 				navigator.serviceWorker.controller.postMessage('clear'); | ||||
| 			} | ||||
| 
 | ||||
| 			navigator.serviceWorker.getRegistrations().then(registrations => { | ||||
| 				registrations.forEach(registration => registration.unregister()); | ||||
|  |  | |||
|  | @ -21,6 +21,21 @@ import urlPreview from './url-preview.vue'; | |||
| import twitterSetting from './twitter-setting.vue'; | ||||
| import fileTypeIcon from './file-type-icon.vue'; | ||||
| 
 | ||||
| //#region widgets
 | ||||
| import wAccessLog from './widgets/access-log.vue'; | ||||
| import wVersion from './widgets/version.vue'; | ||||
| import wRss from './widgets/rss.vue'; | ||||
| import wProfile from './widgets/profile.vue'; | ||||
| import wServer from './widgets/server.vue'; | ||||
| import wBroadcast from './widgets/broadcast.vue'; | ||||
| import wCalendar from './widgets/calendar.vue'; | ||||
| import wPhotoStream from './widgets/photo-stream.vue'; | ||||
| import wSlideshow from './widgets/slideshow.vue'; | ||||
| import wTips from './widgets/tips.vue'; | ||||
| import wDonation from './widgets/donation.vue'; | ||||
| import wNav from './widgets/nav.vue'; | ||||
| //#endregion
 | ||||
| 
 | ||||
| Vue.component('mk-signin', signin); | ||||
| Vue.component('mk-signup', signup); | ||||
| Vue.component('mk-forkit', forkit); | ||||
|  | @ -41,3 +56,18 @@ Vue.component('mk-messaging-room', messagingRoom); | |||
| Vue.component('mk-url-preview', urlPreview); | ||||
| Vue.component('mk-twitter-setting', twitterSetting); | ||||
| Vue.component('mk-file-type-icon', fileTypeIcon); | ||||
| 
 | ||||
| //#region widgets
 | ||||
| Vue.component('mkw-nav', wNav); | ||||
| Vue.component('mkw-calendar', wCalendar); | ||||
| Vue.component('mkw-photo-stream', wPhotoStream); | ||||
| Vue.component('mkw-slideshow', wSlideshow); | ||||
| Vue.component('mkw-tips', wTips); | ||||
| Vue.component('mkw-donation', wDonation); | ||||
| Vue.component('mkw-broadcast', wBroadcast); | ||||
| Vue.component('mkw-profile', wProfile); | ||||
| Vue.component('mkw-server', wServer); | ||||
| Vue.component('mkw-rss', wRss); | ||||
| Vue.component('mkw-version', wVersion); | ||||
| Vue.component('mkw-access-log', wAccessLog); | ||||
| //#endregion
 | ||||
|  |  | |||
|  | @ -1,15 +1,16 @@ | |||
| <template> | ||||
| <div class="mkw-access-log"> | ||||
| 	<template v-if="props.design == 0"> | ||||
| 		<p class="title">%fa:server%%i18n:desktop.tags.mk-access-log-home-widget.title%</p> | ||||
| 	</template> | ||||
| 	<div ref="log"> | ||||
| 		<p v-for="req in requests"> | ||||
| 			<span class="ip" :style="`color:${ req.fg }; background:${ req.bg }`">{{ req.ip }}</span> | ||||
| 			<b>{{ req.method }}</b> | ||||
| 			<span>{{ req.path }}</span> | ||||
| 		</p> | ||||
| 	</div> | ||||
| 	<mk-widget-container :show-header="props.design == 0"> | ||||
| 		<template slot="header">%fa:server%%i18n:desktop.tags.mk-access-log-home-widget.title%</template> | ||||
| 
 | ||||
| 		<div :class="$style.logs" ref="log"> | ||||
| 			<p v-for="req in requests"> | ||||
| 				<span :class="$style.ip" :style="`color:${ req.fg }; background:${ req.bg }`">{{ req.ip }}</span> | ||||
| 				<b>{{ req.method }}</b> | ||||
| 				<span>{{ req.path }}</span> | ||||
| 			</p> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -65,44 +66,25 @@ export default define({ | |||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mkw-access-log | ||||
| 	overflow hidden | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
| 	border-radius 6px | ||||
| <style lang="stylus" module> | ||||
| .logs | ||||
| 	max-height 250px | ||||
| 	overflow auto | ||||
| 
 | ||||
| 	> .title | ||||
| 		z-index 1 | ||||
| 	> p | ||||
| 		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) | ||||
| 		padding 8px | ||||
| 		font-size 0.8em | ||||
| 		color #555 | ||||
| 
 | ||||
| 		> [data-fa] | ||||
| 		&:nth-child(odd) | ||||
| 			background rgba(0, 0, 0, 0.025) | ||||
| 
 | ||||
| 		> b | ||||
| 			margin-right 4px | ||||
| 
 | ||||
| 	> div | ||||
| 		max-height 250px | ||||
| 		overflow auto | ||||
| 
 | ||||
| 		> p | ||||
| 			margin 0 | ||||
| 			padding 8px | ||||
| 			font-size 0.8em | ||||
| 			color #555 | ||||
| 
 | ||||
| 			&:nth-child(odd) | ||||
| 				background rgba(0, 0, 0, 0.025) | ||||
| 
 | ||||
| 			> .ip | ||||
| 				margin-right 4px | ||||
| 				padding 0 4px | ||||
| 
 | ||||
| 			> b | ||||
| 				margin-right 4px | ||||
| .ip | ||||
| 	margin-right 4px | ||||
| 	padding 0 4px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,5 +1,9 @@ | |||
| <template> | ||||
| <div class="mkw-broadcast" :data-found="broadcasts.length != 0" :data-melt="props.design == 1"> | ||||
| <div class="mkw-broadcast" | ||||
| 	:data-found="broadcasts.length != 0" | ||||
| 	:data-melt="props.design == 1" | ||||
| 	:data-mobile="isMobile" | ||||
| > | ||||
| 	<div class="icon"> | ||||
| 		<svg height="32" version="1.1" viewBox="0 0 32 32" width="32"> | ||||
| 			<path class="tower" d="M16.04,11.24c1.79,0,3.239-1.45,3.239-3.24S17.83,4.76,16.04,4.76c-1.79,0-3.24,1.45-3.24,3.24 C12.78,9.78,14.24,11.24,16.04,11.24z M16.04,13.84c-0.82,0-1.66-0.2-2.4-0.6L7.34,29.98h2.98l1.72-2h8l1.681,2H24.7L18.42,13.24 C17.66,13.64,16.859,13.84,16.04,13.84z M16.02,14.8l2.02,7.2h-4L16.02,14.8z M12.04,25.98l2-2h4l2,2H12.04z"></path> | ||||
|  | @ -150,4 +154,8 @@ export default define({ | |||
| 		display block | ||||
| 		font-size 0.7em | ||||
| 
 | ||||
| 	&[data-mobile] | ||||
| 		> p | ||||
| 			color #fff | ||||
| 
 | ||||
| </style> | ||||
|  | @ -2,6 +2,7 @@ | |||
| <div class="mkw-calendar" | ||||
| 	:data-melt="props.design == 1" | ||||
| 	:data-special="special" | ||||
| 	:data-mobile="isMobile" | ||||
| > | ||||
| 	<div class="calendar" :data-is-holiday="isHoliday"> | ||||
| 		<p class="month-and-year"> | ||||
|  | @ -66,6 +67,7 @@ export default define({ | |||
| 	}, | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			if (this.isMobile) return; | ||||
| 			if (this.props.design == 2) { | ||||
| 				this.props.design = 0; | ||||
| 			} else { | ||||
|  | @ -119,6 +121,11 @@ export default define({ | |||
| 		background transparent | ||||
| 		border none | ||||
| 
 | ||||
| 	&[data-mobile] | ||||
| 		border none | ||||
| 		border-radius 8px | ||||
| 		box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) | ||||
| 
 | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		display block | ||||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <div class="mkw-donation"> | ||||
| <div class="mkw-donation" :data-mobile="isMobile"> | ||||
| 	<article> | ||||
| 		<h1>%fa:heart%%i18n:desktop.tags.mk-donation-home-widget.title%</h1> | ||||
| 		<p> | ||||
|  | @ -42,4 +42,17 @@ export default define({ | |||
| 			font-size 0.8em | ||||
| 			color #999 | ||||
| 
 | ||||
| 	&[data-mobile] | ||||
| 		border none | ||||
| 		background #ead8bb | ||||
| 		border-radius 8px | ||||
| 		box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) | ||||
| 
 | ||||
| 		> article | ||||
| 			> h1 | ||||
| 				color #7b8871 | ||||
| 
 | ||||
| 			> p | ||||
| 				color #777d71 | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,6 +1,10 @@ | |||
| <template> | ||||
| <div class="mkw-nav"> | ||||
| 	<mk-nav/> | ||||
| 	<mk-widget-container> | ||||
| 		<div :class="$style.body"> | ||||
| 			<mk-nav/> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -11,14 +15,12 @@ export default define({ | |||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mkw-nav | ||||
| <style lang="stylus" module> | ||||
| .body | ||||
| 	padding 16px | ||||
| 	font-size 12px | ||||
| 	color #aaa | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
| 	border-radius 6px | ||||
| 
 | ||||
| 	a | ||||
| 		color #999 | ||||
|  | @ -0,0 +1,104 @@ | |||
| <template> | ||||
| <div class="mkw-photo-stream" :class="$style.root" :data-melt="props.design == 2"> | ||||
| 	<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> | ||||
| 		<template slot="header">%fa:camera%%i18n:desktop.tags.mk-photo-stream-home-widget.title%</template> | ||||
| 
 | ||||
| 		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<div :class="$style.stream" v-if="!fetching && images.length > 0"> | ||||
| 			<div v-for="image in images" :key="image.id" :class="$style.img" :style="`background-image: url(${image.url}?thumbnail&size=256)`"></div> | ||||
| 		</div> | ||||
| 		<p :class="$style.empty" v-if="!fetching && images.length == 0">%i18n:desktop.tags.mk-photo-stream-home-widget.no-photos%</p> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| export default define({ | ||||
| 	name: 'photo-stream', | ||||
| 	props: () => ({ | ||||
| 		design: 0 | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			images: [], | ||||
| 			fetching: true, | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection = (this as any).os.stream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.stream.use(); | ||||
| 
 | ||||
| 		this.connection.on('drive_file_created', this.onDriveFileCreated); | ||||
| 
 | ||||
| 		(this as any).api('drive/stream', { | ||||
| 			type: 'image/*', | ||||
| 			limit: 9 | ||||
| 		}).then(images => { | ||||
| 			this.images = images; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('drive_file_created', this.onDriveFileCreated); | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onDriveFileCreated(file) { | ||||
| 			if (/^image\/.+$/.test(file.type)) { | ||||
| 				this.images.unshift(file); | ||||
| 				if (this.images.length > 9) this.images.pop(); | ||||
| 			} | ||||
| 		}, | ||||
| 		func() { | ||||
| 			if (this.props.design == 2) { | ||||
| 				this.props.design = 0; | ||||
| 			} else { | ||||
| 				this.props.design++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" module> | ||||
| .root[data-melt] | ||||
| 	.stream | ||||
| 		padding 0 | ||||
| 
 | ||||
| 	.img | ||||
| 		border solid 4px transparent | ||||
| 		border-radius 8px | ||||
| 
 | ||||
| .stream | ||||
| 	display -webkit-flex | ||||
| 	display -moz-flex | ||||
| 	display -ms-flex | ||||
| 	display flex | ||||
| 	justify-content center | ||||
| 	flex-wrap wrap | ||||
| 	padding 8px | ||||
| 
 | ||||
| 	.img | ||||
| 		flex 1 1 33% | ||||
| 		width 33% | ||||
| 		height 80px | ||||
| 		background-position center center | ||||
| 		background-size cover | ||||
| 		border solid 2px transparent | ||||
| 		border-radius 4px | ||||
| 
 | ||||
| .fetching | ||||
| .empty | ||||
| 	margin 0 | ||||
| 	padding 16px | ||||
| 	text-align center | ||||
| 	color #aaa | ||||
| 
 | ||||
| 	> [data-fa] | ||||
| 		margin-right 4px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -0,0 +1,93 @@ | |||
| <template> | ||||
| <div class="mkw-rss" :data-mobile="isMobile"> | ||||
| 	<mk-widget-container :show-header="!props.compact"> | ||||
| 		<template slot="header">%fa:rss-square%RSS</template> | ||||
| 		<button slot="func" title="設定" @click="setting">%fa:cog%</button> | ||||
| 
 | ||||
| 		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<div :class="$style.feed" v-else> | ||||
| 			<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| export default define({ | ||||
| 	name: 'rss', | ||||
| 	props: () => ({ | ||||
| 		compact: false | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			url: 'http://news.yahoo.co.jp/pickup/rss.xml', | ||||
| 			items: [], | ||||
| 			fetching: true, | ||||
| 			clock: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 		this.clock = setInterval(this.fetch, 60000); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		clearInterval(this.clock); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			this.props.compact = !this.props.compact; | ||||
| 		}, | ||||
| 		fetch() { | ||||
| 			fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, { | ||||
| 				cache: 'no-cache' | ||||
| 			}).then(res => { | ||||
| 				res.json().then(feed => { | ||||
| 					this.items = feed.items; | ||||
| 					this.fetching = false; | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
| 		setting() { | ||||
| 			alert('not implemented yet'); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" module> | ||||
| .feed | ||||
| 	padding 12px 16px | ||||
| 	font-size 0.9em | ||||
| 
 | ||||
| 	> a | ||||
| 		display block | ||||
| 		padding 4px 0 | ||||
| 		color #666 | ||||
| 		border-bottom dashed 1px #eee | ||||
| 
 | ||||
| 		&:last-child | ||||
| 			border-bottom none | ||||
| 
 | ||||
| .fetching | ||||
| 	margin 0 | ||||
| 	padding 16px | ||||
| 	text-align center | ||||
| 	color #aaa | ||||
| 
 | ||||
| 	> [data-fa] | ||||
| 		margin-right 4px | ||||
| 
 | ||||
| &[data-mobile] | ||||
| 	.feed | ||||
| 		padding 0 | ||||
| 		font-size 1em | ||||
| 
 | ||||
| 		> a | ||||
| 			padding 8px 16px | ||||
| 
 | ||||
| 			&:nth-child(even) | ||||
| 				background #e2e2e2 | ||||
| 
 | ||||
| </style> | ||||
|  | @ -0,0 +1,93 @@ | |||
| <template> | ||||
| <div class="mkw-server"> | ||||
| 	<mk-widget-container :show-header="props.design == 0" :naked="props.design == 2"> | ||||
| 		<template slot="header">%fa:server%%i18n:desktop.tags.mk-server-home-widget.title%</template> | ||||
| 		<button slot="func" @click="toggle" title="%i18n:desktop.tags.mk-server-home-widget.toggle%">%fa:sort%</button> | ||||
| 
 | ||||
| 		<p :class="$style.fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 		<template v-if="!fetching"> | ||||
| 			<x-cpu-memory v-show="props.view == 0" :connection="connection"/> | ||||
| 			<x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/> | ||||
| 			<x-memory v-show="props.view == 2" :connection="connection"/> | ||||
| 			<x-disk v-show="props.view == 3" :connection="connection"/> | ||||
| 			<x-uptimes v-show="props.view == 4" :connection="connection"/> | ||||
| 			<x-info v-show="props.view == 5" :connection="connection" :meta="meta"/> | ||||
| 		</template> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| import XCpuMemory from './server.cpu-memory.vue'; | ||||
| import XCpu from './server.cpu.vue'; | ||||
| import XMemory from './server.memory.vue'; | ||||
| import XDisk from './server.disk.vue'; | ||||
| import XUptimes from './server.uptimes.vue'; | ||||
| import XInfo from './server.info.vue'; | ||||
| 
 | ||||
| export default define({ | ||||
| 	name: 'server', | ||||
| 	props: () => ({ | ||||
| 		design: 0, | ||||
| 		view: 0 | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	components: { | ||||
| 		XCpuMemory, | ||||
| 		XCpu, | ||||
| 		XMemory, | ||||
| 		XDisk, | ||||
| 		XUptimes, | ||||
| 		XInfo | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			meta: null, | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.meta = meta; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 
 | ||||
| 		this.connection = (this as any).os.streams.serverStream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.streams.serverStream.use(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		(this as any).os.streams.serverStream.dispose(this.connectionId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		toggle() { | ||||
| 			if (this.props.view == 5) { | ||||
| 				this.props.view = 0; | ||||
| 			} else { | ||||
| 				this.props.view++; | ||||
| 			} | ||||
| 		}, | ||||
| 		func() { | ||||
| 			if (this.props.design == 2) { | ||||
| 				this.props.design = 0; | ||||
| 			} else { | ||||
| 				this.props.design++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" module> | ||||
| .fetching | ||||
| 	margin 0 | ||||
| 	padding 16px | ||||
| 	text-align center | ||||
| 	color #aaa | ||||
| 
 | ||||
| 	> [data-fa] | ||||
| 		margin-right 4px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -27,27 +27,19 @@ import friendsMaker from './friends-maker.vue'; | |||
| import followers from './followers.vue'; | ||||
| import following from './following.vue'; | ||||
| import usersList from './users-list.vue'; | ||||
| import wNav from './widgets/nav.vue'; | ||||
| import wCalendar from './widgets/calendar.vue'; | ||||
| import wPhotoStream from './widgets/photo-stream.vue'; | ||||
| import wSlideshow from './widgets/slideshow.vue'; | ||||
| import wTips from './widgets/tips.vue'; | ||||
| import wDonation from './widgets/donation.vue'; | ||||
| import widgetContainer from './widget-container.vue'; | ||||
| 
 | ||||
| //#region widgets
 | ||||
| import wNotifications from './widgets/notifications.vue'; | ||||
| import wBroadcast from './widgets/broadcast.vue'; | ||||
| import wTimemachine from './widgets/timemachine.vue'; | ||||
| import wProfile from './widgets/profile.vue'; | ||||
| import wServer from './widgets/server.vue'; | ||||
| import wActivity from './widgets/activity.vue'; | ||||
| import wRss from './widgets/rss.vue'; | ||||
| import wTrends from './widgets/trends.vue'; | ||||
| import wVersion from './widgets/version.vue'; | ||||
| import wUsers from './widgets/users.vue'; | ||||
| import wPolls from './widgets/polls.vue'; | ||||
| import wPostForm from './widgets/post-form.vue'; | ||||
| import wMessaging from './widgets/messaging.vue'; | ||||
| import wChannel from './widgets/channel.vue'; | ||||
| import wAccessLog from './widgets/access-log.vue'; | ||||
| //#endregion
 | ||||
| 
 | ||||
| Vue.component('mk-ui', ui); | ||||
| Vue.component('mk-ui-notification', uiNotification); | ||||
|  | @ -76,24 +68,16 @@ Vue.component('mk-friends-maker', friendsMaker); | |||
| Vue.component('mk-followers', followers); | ||||
| Vue.component('mk-following', following); | ||||
| Vue.component('mk-users-list', usersList); | ||||
| Vue.component('mkw-nav', wNav); | ||||
| Vue.component('mkw-calendar', wCalendar); | ||||
| Vue.component('mkw-photo-stream', wPhotoStream); | ||||
| Vue.component('mkw-slideshow', wSlideshow); | ||||
| Vue.component('mkw-tips', wTips); | ||||
| Vue.component('mkw-donation', wDonation); | ||||
| Vue.component('mk-widget-container', widgetContainer); | ||||
| 
 | ||||
| //#region widgets
 | ||||
| Vue.component('mkw-notifications', wNotifications); | ||||
| Vue.component('mkw-broadcast', wBroadcast); | ||||
| Vue.component('mkw-timemachine', wTimemachine); | ||||
| Vue.component('mkw-profile', wProfile); | ||||
| Vue.component('mkw-server', wServer); | ||||
| Vue.component('mkw-activity', wActivity); | ||||
| Vue.component('mkw-rss', wRss); | ||||
| Vue.component('mkw-trends', wTrends); | ||||
| Vue.component('mkw-version', wVersion); | ||||
| Vue.component('mkw-users', wUsers); | ||||
| Vue.component('mkw-polls', wPolls); | ||||
| Vue.component('mkw-post-form', wPostForm); | ||||
| Vue.component('mkw-messaging', wMessaging); | ||||
| Vue.component('mkw-channel', wChannel); | ||||
| Vue.component('mkw-access-log', wAccessLog); | ||||
| //#endregion
 | ||||
|  |  | |||
|  | @ -0,0 +1,72 @@ | |||
| <template> | ||||
| <div class="mk-widget-container" :class="{ naked }"> | ||||
| 	<header v-if="showHeader"> | ||||
| 		<div class="title"><slot name="header"></slot></div> | ||||
| 		<slot name="func"></slot> | ||||
| 	</header> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		showHeader: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		naked: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-widget-container | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
| 	border-radius 6px | ||||
| 	overflow hidden | ||||
| 
 | ||||
| 	&.naked | ||||
| 		background transparent !important | ||||
| 		border none !important | ||||
| 
 | ||||
| 	> header | ||||
| 		> .title | ||||
| 			z-index 1 | ||||
| 			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 | ||||
| 
 | ||||
| 			&:empty | ||||
| 				display none | ||||
| 
 | ||||
| 		> 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 | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,122 +0,0 @@ | |||
| <template> | ||||
| <div class="mkw-photo-stream" :data-melt="props.design == 2"> | ||||
| 	<p class="title" v-if="props.design == 0">%fa:camera%%i18n:desktop.tags.mk-photo-stream-home-widget.title%</p> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<div class="stream" v-if="!fetching && images.length > 0"> | ||||
| 		<div v-for="image in images" :key="image.id" class="img" :style="`background-image: url(${image.url}?thumbnail&size=256)`"></div> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="!fetching && images.length == 0">%i18n:desktop.tags.mk-photo-stream-home-widget.no-photos%</p> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| export default define({ | ||||
| 	name: 'photo-stream', | ||||
| 	props: () => ({ | ||||
| 		design: 0 | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			images: [], | ||||
| 			fetching: true, | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection = (this as any).os.stream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.stream.use(); | ||||
| 
 | ||||
| 		this.connection.on('drive_file_created', this.onDriveFileCreated); | ||||
| 
 | ||||
| 		(this as any).api('drive/stream', { | ||||
| 			type: 'image/*', | ||||
| 			limit: 9 | ||||
| 		}).then(images => { | ||||
| 			this.images = images; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.off('drive_file_created', this.onDriveFileCreated); | ||||
| 		(this as any).os.stream.dispose(this.connectionId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onDriveFileCreated(file) { | ||||
| 			if (/^image\/.+$/.test(file.type)) { | ||||
| 				this.images.unshift(file); | ||||
| 				if (this.images.length > 9) this.images.pop(); | ||||
| 			} | ||||
| 		}, | ||||
| 		func() { | ||||
| 			if (this.props.design == 2) { | ||||
| 				this.props.design = 0; | ||||
| 			} else { | ||||
| 				this.props.design++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mkw-photo-stream | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
| 	border-radius 6px | ||||
| 
 | ||||
| 	&[data-melt] | ||||
| 		background transparent !important | ||||
| 		border none !important | ||||
| 
 | ||||
| 		> .stream | ||||
| 			padding 0 | ||||
| 
 | ||||
| 			> .img | ||||
| 				border solid 4px transparent | ||||
| 				border-radius 8px | ||||
| 
 | ||||
| 	> .title | ||||
| 		z-index 1 | ||||
| 		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 | ||||
| 
 | ||||
| 	> .stream | ||||
| 		display -webkit-flex | ||||
| 		display -moz-flex | ||||
| 		display -ms-flex | ||||
| 		display flex | ||||
| 		justify-content center | ||||
| 		flex-wrap wrap | ||||
| 		padding 8px | ||||
| 
 | ||||
| 		> .img | ||||
| 			flex 1 1 33% | ||||
| 			width 33% | ||||
| 			height 80px | ||||
| 			background-position center center | ||||
| 			background-size cover | ||||
| 			border solid 2px transparent | ||||
| 			border-radius 4px | ||||
| 
 | ||||
| 	> .fetching | ||||
| 	> .empty | ||||
| 		margin 0 | ||||
| 		padding 16px | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
| 
 | ||||
| 		> [data-fa] | ||||
| 			margin-right 4px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,111 +0,0 @@ | |||
| <template> | ||||
| <div class="mkw-rss"> | ||||
| 	<template v-if="!props.compact"> | ||||
| 		<p class="title">%fa:rss-square%RSS</p> | ||||
| 		<button title="設定">%fa:cog%</button> | ||||
| 	</template> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<div class="feed" v-else> | ||||
| 		<a v-for="item in items" :href="item.link" target="_blank">{{ item.title }}</a> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| export default define({ | ||||
| 	name: 'rss', | ||||
| 	props: () => ({ | ||||
| 		compact: false | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	data() { | ||||
| 		return { | ||||
| 			url: 'http://news.yahoo.co.jp/pickup/rss.xml', | ||||
| 			items: [], | ||||
| 			fetching: true, | ||||
| 			clock: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 		this.clock = setInterval(this.fetch, 60000); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		clearInterval(this.clock); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		func() { | ||||
| 			this.props.compact = !this.props.compact; | ||||
| 		}, | ||||
| 		fetch() { | ||||
| 			fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.url}`, { | ||||
| 				cache: 'no-cache' | ||||
| 			}).then(res => { | ||||
| 				res.json().then(feed => { | ||||
| 					this.items = feed.items; | ||||
| 					this.fetching = false; | ||||
| 				}); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mkw-rss | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
| 	border-radius 6px | ||||
| 
 | ||||
| 	> .title | ||||
| 		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 | ||||
| 		top 0 | ||||
| 		right 0 | ||||
| 		padding 0 | ||||
| 		width 42px | ||||
| 		font-size 0.9em | ||||
| 		line-height 42px | ||||
| 		color #ccc | ||||
| 
 | ||||
| 		&:hover | ||||
| 			color #aaa | ||||
| 
 | ||||
| 		&:active | ||||
| 			color #999 | ||||
| 
 | ||||
| 	> .feed | ||||
| 		padding 12px 16px | ||||
| 		font-size 0.9em | ||||
| 
 | ||||
| 		> a | ||||
| 			display block | ||||
| 			padding 4px 0 | ||||
| 			color #666 | ||||
| 			border-bottom dashed 1px #eee | ||||
| 
 | ||||
| 			&:last-child | ||||
| 				border-bottom none | ||||
| 
 | ||||
| 	> .fetching | ||||
| 		margin 0 | ||||
| 		padding 16px | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
| 
 | ||||
| 		> [data-fa] | ||||
| 			margin-right 4px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,131 +0,0 @@ | |||
| <template> | ||||
| <div class="mkw-server" :data-melt="props.design == 2"> | ||||
| 	<template v-if="props.design == 0"> | ||||
| 		<p class="title">%fa:server%%i18n:desktop.tags.mk-server-home-widget.title%</p> | ||||
| 		<button @click="toggle" title="%i18n:desktop.tags.mk-server-home-widget.toggle%">%fa:sort%</button> | ||||
| 	</template> | ||||
| 	<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> | ||||
| 	<template v-if="!fetching"> | ||||
| 		<x-cpu-memory v-show="props.view == 0" :connection="connection"/> | ||||
| 		<x-cpu v-show="props.view == 1" :connection="connection" :meta="meta"/> | ||||
| 		<x-memory v-show="props.view == 2" :connection="connection"/> | ||||
| 		<x-disk v-show="props.view == 3" :connection="connection"/> | ||||
| 		<x-uptimes v-show="props.view == 4" :connection="connection"/> | ||||
| 		<x-info v-show="props.view == 5" :connection="connection" :meta="meta"/> | ||||
| 	</template> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| import XCpuMemory from './server.cpu-memory.vue'; | ||||
| import XCpu from './server.cpu.vue'; | ||||
| import XMemory from './server.memory.vue'; | ||||
| import XDisk from './server.disk.vue'; | ||||
| import XUptimes from './server.uptimes.vue'; | ||||
| import XInfo from './server.info.vue'; | ||||
| 
 | ||||
| export default define({ | ||||
| 	name: 'server', | ||||
| 	props: () => ({ | ||||
| 		design: 0, | ||||
| 		view: 0 | ||||
| 	}) | ||||
| }).extend({ | ||||
| 	components: { | ||||
| 		XCpuMemory, | ||||
| 		XCpu, | ||||
| 		XMemory, | ||||
| 		XDisk, | ||||
| 		XUptimes, | ||||
| 		XInfo | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			fetching: true, | ||||
| 			meta: null, | ||||
| 			connection: null, | ||||
| 			connectionId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		(this as any).os.getMeta().then(meta => { | ||||
| 			this.meta = meta; | ||||
| 			this.fetching = false; | ||||
| 		}); | ||||
| 
 | ||||
| 		this.connection = (this as any).os.streams.serverStream.getConnection(); | ||||
| 		this.connectionId = (this as any).os.streams.serverStream.use(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		(this as any).os.streams.serverStream.dispose(this.connectionId); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		toggle() { | ||||
| 			if (this.props.view == 5) { | ||||
| 				this.props.view = 0; | ||||
| 			} else { | ||||
| 				this.props.view++; | ||||
| 			} | ||||
| 		}, | ||||
| 		func() { | ||||
| 			if (this.props.design == 2) { | ||||
| 				this.props.design = 0; | ||||
| 			} else { | ||||
| 				this.props.design++; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mkw-server | ||||
| 	background #fff | ||||
| 	border solid 1px rgba(0, 0, 0, 0.075) | ||||
| 	border-radius 6px | ||||
| 
 | ||||
| 	&[data-melt] | ||||
| 		background transparent !important | ||||
| 		border none !important | ||||
| 
 | ||||
| 	> .title | ||||
| 		z-index 1 | ||||
| 		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 | ||||
| 
 | ||||
| 	> .fetching | ||||
| 		margin 0 | ||||
| 		padding 16px | ||||
| 		text-align center | ||||
| 		color #aaa | ||||
| 
 | ||||
| 		> [data-fa] | ||||
| 			margin-right 4px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| <div class="root activity"> | ||||
| <div class="mk-activity"> | ||||
| 	<svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none"> | ||||
| 		<g v-for="(d, i) in data"> | ||||
| 			<rect width="0.8" :height="d.postsH" | ||||
|  | @ -47,7 +47,7 @@ export default Vue.extend({ | |||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .root.activity | ||||
| .mk-activity | ||||
| 	max-width 600px | ||||
| 	margin 0 auto | ||||
| 
 | ||||
|  | @ -1,29 +0,0 @@ | |||
| <template> | ||||
| <div class="mk-home"> | ||||
| 	<mk-timeline @loaded="onTlLoaded"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	methods: { | ||||
| 		onTlLoaded() { | ||||
| 			this.$emit('loaded'); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-home | ||||
| 
 | ||||
| 	> .mk-timeline | ||||
| 		max-width 600px | ||||
| 		margin 0 auto | ||||
| 		padding 8px | ||||
| 
 | ||||
| 	@media (min-width 500px) | ||||
| 		padding 16px | ||||
| 
 | ||||
| </style> | ||||
|  | @ -1,7 +1,6 @@ | |||
| import Vue from 'vue'; | ||||
| 
 | ||||
| import ui from './ui.vue'; | ||||
| import home from './home.vue'; | ||||
| import timeline from './timeline.vue'; | ||||
| import posts from './posts.vue'; | ||||
| import imagesImage from './images-image.vue'; | ||||
|  | @ -19,9 +18,14 @@ import notificationPreview from './notification-preview.vue'; | |||
| import usersList from './users-list.vue'; | ||||
| import userPreview from './user-preview.vue'; | ||||
| import userTimeline from './user-timeline.vue'; | ||||
| import activity from './activity.vue'; | ||||
| import widgetContainer from './widget-container.vue'; | ||||
| 
 | ||||
| //#region widgets
 | ||||
| import wActivity from './widgets/activity.vue'; | ||||
| //#endregion
 | ||||
| 
 | ||||
| Vue.component('mk-ui', ui); | ||||
| Vue.component('mk-home', home); | ||||
| Vue.component('mk-timeline', timeline); | ||||
| Vue.component('mk-posts', posts); | ||||
| Vue.component('mk-images-image', imagesImage); | ||||
|  | @ -39,3 +43,9 @@ Vue.component('mk-notification-preview', notificationPreview); | |||
| Vue.component('mk-users-list', usersList); | ||||
| Vue.component('mk-user-preview', userPreview); | ||||
| Vue.component('mk-user-timeline', userTimeline); | ||||
| Vue.component('mk-activity', activity); | ||||
| Vue.component('mk-widget-container', widgetContainer); | ||||
| 
 | ||||
| //#region widgets
 | ||||
| Vue.component('mkw-activity', wActivity); | ||||
| //#endregion
 | ||||
|  |  | |||
|  | @ -9,9 +9,7 @@ | |||
| 			<h1> | ||||
| 				<slot>Misskey</slot> | ||||
| 			</h1> | ||||
| 			<button v-if="func" @click="func"> | ||||
| 				<slot name="funcIcon"></slot> | ||||
| 			</button> | ||||
| 			<slot name="func"></slot> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| <template> | ||||
| <div class="mk-ui"> | ||||
| 	<x-header :func="func"> | ||||
| 		<template slot="funcIcon"><slot name="funcIcon"></slot></template> | ||||
| 	<x-header> | ||||
| 		<template slot="func"><slot name="func"></slot></template> | ||||
| 		<slot name="header"></slot> | ||||
| 	</x-header> | ||||
| 	<x-nav :is-open="isDrawerOpening"/> | ||||
|  | @ -23,7 +23,7 @@ export default Vue.extend({ | |||
| 		XHeader, | ||||
| 		XNav | ||||
| 	}, | ||||
| 	props: ['title', 'func'], | ||||
| 	props: ['title'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			isDrawerOpening: false, | ||||
|  |  | |||
|  | @ -0,0 +1,65 @@ | |||
| <template> | ||||
| <div class="mk-widget-container" :class="{ naked }"> | ||||
| 	<header v-if="showHeader"> | ||||
| 		<div class="title"><slot name="header"></slot></div> | ||||
| 		<slot name="func"></slot> | ||||
| 	</header> | ||||
| 	<slot></slot> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
| 		showHeader: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		naked: { | ||||
| 			type: Boolean, | ||||
| 			default: false | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .mk-widget-container | ||||
| 	background #eee | ||||
| 	border-radius 8px | ||||
| 	box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) | ||||
| 	overflow hidden | ||||
| 
 | ||||
| 	&.naked | ||||
| 		background transparent !important | ||||
| 		border none !important | ||||
| 
 | ||||
| 	> header | ||||
| 		> .title | ||||
| 			margin 0 | ||||
| 			padding 8px 10px | ||||
| 			font-size 15px | ||||
| 			font-weight normal | ||||
| 			color #465258 | ||||
| 			background #fff | ||||
| 			border-radius 8px 8px 0 0 | ||||
| 
 | ||||
| 			> [data-fa] | ||||
| 				margin-right 6px | ||||
| 
 | ||||
| 			&:empty | ||||
| 				display none | ||||
| 
 | ||||
| 		> button | ||||
| 			position absolute | ||||
| 			z-index 2 | ||||
| 			top 0 | ||||
| 			right 0 | ||||
| 			padding 0 | ||||
| 			width 42px | ||||
| 			height 100% | ||||
| 			font-size 15px | ||||
| 			color #465258 | ||||
| 
 | ||||
| </style> | ||||
|  | @ -0,0 +1,23 @@ | |||
| <template> | ||||
| <div class="mkw-activity"> | ||||
| 	<mk-widget-container> | ||||
| 		<template slot="header">%fa:chart-bar%アクティビティ</template> | ||||
| 		<div :class="$style.body"> | ||||
| 			<mk-activity :user="os.i"/> | ||||
| 		</div> | ||||
| 	</mk-widget-container> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import define from '../../../../common/define-widget'; | ||||
| 
 | ||||
| export default define({ | ||||
| 	name: 'activity', | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" module> | ||||
| .body | ||||
| 	padding 8px | ||||
| </style> | ||||
|  | @ -1,11 +1,11 @@ | |||
| <template> | ||||
| <mk-ui :func="fn"> | ||||
| <mk-ui> | ||||
| 	<span slot="header"> | ||||
| 		<template v-if="folder">%fa:R folder-open%{{ folder.name }}</template> | ||||
| 		<template v-if="file"><mk-file-type-icon class="icon" :type="file.type"/>{{ file.name }}</template> | ||||
| 		<template v-if="!folder && !file">%fa:cloud%%i18n:mobile.tags.mk-drive-page.drive%</template> | ||||
| 	</span> | ||||
| 	<template slot="funcIcon">%fa:ellipsis-h%</template> | ||||
| 	<template slot="func"><button @click="fn">%fa:ellipsis-h%</button></template> | ||||
| 	<mk-drive | ||||
| 		ref="browser" | ||||
| 		:init-folder="initFolder" | ||||
|  |  | |||
|  | @ -1,24 +1,112 @@ | |||
| <template> | ||||
| <mk-ui :func="fn"> | ||||
| 	<span slot="header">%fa:home%%i18n:mobile.tags.mk-home.home%</span> | ||||
| 	<template slot="funcIcon">%fa:pencil-alt%</template> | ||||
| 	<mk-home @loaded="onHomeLoaded"/> | ||||
| <mk-ui> | ||||
| 	<span slot="header" @click="showTl = !showTl"> | ||||
| 		<template v-if="showTl">%fa:home%タイムライン</template> | ||||
| 		<template v-else>%fa:home%ウィジェット</template> | ||||
| 		<span style="margin-left:8px"> | ||||
| 			<template v-if="showTl">%fa:angle-down%</template> | ||||
| 			<template v-else>%fa:angle-up%</template> | ||||
| 		</span> | ||||
| 	</span> | ||||
| 	<template slot="func"> | ||||
| 		<button @click="fn" v-if="showTl">%fa:pencil-alt%</button> | ||||
| 		<button @click="customizing = !customizing" v-else>%fa:cog%</button> | ||||
| 	</template> | ||||
| 	<main> | ||||
| 		<div class="tl"> | ||||
| 			<mk-timeline @loaded="onLoaded" v-show="showTl"/> | ||||
| 		</div> | ||||
| 		<div class="widgets" v-if="!showTl"> | ||||
| 			<template v-if="customizing"> | ||||
| 				<header> | ||||
| 					<select v-model="widgetAdderSelected"> | ||||
| 						<option value="profile">プロフィール</option> | ||||
| 						<option value="calendar">カレンダー</option> | ||||
| 						<option value="activity">アクティビティ</option> | ||||
| 						<option value="rss">RSSリーダー</option> | ||||
| 						<option value="photo-stream">フォトストリーム</option> | ||||
| 						<option value="version">バージョン</option> | ||||
| 						<option value="access-log">アクセスログ</option> | ||||
| 						<option value="server">サーバー情報</option> | ||||
| 						<option value="donation">寄付のお願い</option> | ||||
| 						<option value="nav">ナビゲーション</option> | ||||
| 						<option value="tips">ヒント</option> | ||||
| 					</select> | ||||
| 					<button @click="addWidget">追加</button> | ||||
| 					<p>移動するには「三」をドラッグします。削除するには「x」をタップします。</p> | ||||
| 				</header> | ||||
| 				<x-draggable | ||||
| 					:list="widgets" | ||||
| 					:options="{ handle: '.handle', animation: 150 }" | ||||
| 					@sort="onWidgetSort" | ||||
| 				> | ||||
| 					<div v-for="widget in widgets" class="customize-container" :key="widget.id"> | ||||
| 						<header> | ||||
| 							<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button> | ||||
| 						</header> | ||||
| 						<div> | ||||
| 							<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-mobile="true"/> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</x-draggable> | ||||
| 			</template> | ||||
| 			<template v-else> | ||||
| 				<component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :is-mobile="true" @chosen="warp"/> | ||||
| 			</template> | ||||
| 		</div> | ||||
| 	</main> | ||||
| </mk-ui> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import * as XDraggable from 'vuedraggable'; | ||||
| import * as uuid from 'uuid'; | ||||
| import Progress from '../../../common/scripts/loading'; | ||||
| import getPostSummary from '../../../../../common/get-post-summary'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XDraggable | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			connection: null, | ||||
| 			connectionId: null, | ||||
| 			unreadCount: 0 | ||||
| 			unreadCount: 0, | ||||
| 			showTl: true, | ||||
| 			widgets: [], | ||||
| 			customizing: false, | ||||
| 			widgetAdderSelected: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		if ((this as any).os.i.client_settings.mobile_home == null) { | ||||
| 			Vue.set((this as any).os.i.client_settings, 'mobile_home',  [{ | ||||
| 				name: 'calendar', | ||||
| 				id: 'a' | ||||
| 			}, { | ||||
| 				name: 'activity', | ||||
| 				id: 'b' | ||||
| 			}, { | ||||
| 				name: 'rss', | ||||
| 				id: 'c' | ||||
| 			}, { | ||||
| 				name: 'photo-stream', | ||||
| 				id: 'd' | ||||
| 			}, { | ||||
| 				name: 'donation', | ||||
| 				id: 'e' | ||||
| 			}, { | ||||
| 				name: 'nav', | ||||
| 				id: 'f' | ||||
| 			}, { | ||||
| 				name: 'version', | ||||
| 				id: 'g' | ||||
| 			}]); | ||||
| 		} | ||||
| 		this.widgets = (this as any).os.i.client_settings.mobile_home; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		document.title = 'Misskey'; | ||||
| 		document.documentElement.style.background = '#313a42'; | ||||
|  | @ -40,7 +128,7 @@ export default Vue.extend({ | |||
| 		fn() { | ||||
| 			(this as any).apis.post(); | ||||
| 		}, | ||||
| 		onHomeLoaded() { | ||||
| 		onLoaded() { | ||||
| 			Progress.done(); | ||||
| 		}, | ||||
| 		onStreamPost(post) { | ||||
|  | @ -54,7 +142,81 @@ export default Vue.extend({ | |||
| 				this.unreadCount = 0; | ||||
| 				document.title = 'Misskey'; | ||||
| 			} | ||||
| 		}, | ||||
| 		onWidgetSort() { | ||||
| 			this.saveHome(); | ||||
| 		}, | ||||
| 		addWidget() { | ||||
| 			const widget = { | ||||
| 				name: this.widgetAdderSelected, | ||||
| 				id: uuid(), | ||||
| 				data: {} | ||||
| 			}; | ||||
| 
 | ||||
| 			this.widgets.unshift(widget); | ||||
| 			this.saveHome(); | ||||
| 		}, | ||||
| 		removeWidget(widget) { | ||||
| 			this.widgets = this.widgets.filter(w => w.id != widget.id); | ||||
| 			this.saveHome(); | ||||
| 		}, | ||||
| 		saveHome() { | ||||
| 			(this as any).api('i/update_mobile_home', { | ||||
| 				home: this.widgets | ||||
| 			}); | ||||
| 		}, | ||||
| 		warp() { | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| main | ||||
| 
 | ||||
| 	> .tl | ||||
| 		> .mk-timeline | ||||
| 			max-width 600px | ||||
| 			margin 0 auto | ||||
| 			padding 8px | ||||
| 
 | ||||
| 			@media (min-width 500px) | ||||
| 				padding 16px | ||||
| 
 | ||||
| 	> .widgets | ||||
| 		margin 0 auto | ||||
| 		max-width 500px | ||||
| 
 | ||||
| 		> header | ||||
| 			padding 8px | ||||
| 			background #fff | ||||
| 
 | ||||
| 		.widget | ||||
| 			margin 8px | ||||
| 
 | ||||
| 		.customize-container | ||||
| 			margin 8px | ||||
| 			background #fff | ||||
| 
 | ||||
| 			> header | ||||
| 				line-height 32px | ||||
| 				background #eee | ||||
| 
 | ||||
| 				> .handle | ||||
| 					padding 0 8px | ||||
| 
 | ||||
| 				> .remove | ||||
| 					position absolute | ||||
| 					top 0 | ||||
| 					right 0 | ||||
| 					padding 0 8px | ||||
| 					line-height 32px | ||||
| 
 | ||||
| 			> div | ||||
| 				padding 8px | ||||
| 
 | ||||
| 				> * | ||||
| 					pointer-events none | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| <template> | ||||
| <mk-ui :func="fn"> | ||||
| <mk-ui> | ||||
| 	<span slot="header">%fa:R bell%%i18n:mobile.tags.mk-notifications-page.notifications%</span> | ||||
| 	<span slot="funcIcon">%fa:check%</span> | ||||
| 	<template slot="func"><button @click="fn">%fa:check%</button></template> | ||||
| 	<mk-notifications @fetched="onFetched"/> | ||||
| </mk-ui> | ||||
| </template> | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| <template> | ||||
| <mk-ui> | ||||
| 	<span slot="header" v-if="!fetching">%fa:user% {{ user.name }}</span> | ||||
| 	<template slot="funcIcon">%fa:pencil-alt%</template> | ||||
| 	<main v-if="!fetching"> | ||||
| 		<header> | ||||
| 			<div class="banner" :style="user.banner_url ? `background-image: url(${user.banner_url}?thumbnail&size=1024)` : ''"></div> | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 	<section class="activity"> | ||||
| 		<h2>%fa:chart-bar%%i18n:mobile.tags.mk-user-overview.activity%</h2> | ||||
| 		<div> | ||||
| 			<x-activity :user="user"/> | ||||
| 			<mk-activity :user="user"/> | ||||
| 		</div> | ||||
| 	</section> | ||||
| 	<section class="frequently-replied-users"> | ||||
|  | @ -41,15 +41,13 @@ import XPosts from './home.posts.vue'; | |||
| import XPhotos from './home.photos.vue'; | ||||
| import XFriends from './home.friends.vue'; | ||||
| import XFollowersYouKnow from './home.followers-you-know.vue'; | ||||
| import XActivity from './home.activity.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	components: { | ||||
| 		XPosts, | ||||
| 		XPhotos, | ||||
| 		XFriends, | ||||
| 		XFollowersYouKnow, | ||||
| 		XActivity | ||||
| 		XFollowersYouKnow | ||||
| 	}, | ||||
| 	props: ['user'] | ||||
| }); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue