wip
This commit is contained in:
		
							parent
							
								
									bc8a0083e2
								
							
						
					
					
						commit
						6ab0c386cb
					
				
					 6 changed files with 432 additions and 326 deletions
				
			
		| 
						 | 
				
			
			@ -15,6 +15,7 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
 | 
			
		||||
const fetchLimit = 10;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +34,7 @@ export default Vue.extend({
 | 
			
		|||
			existMore: false,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null,
 | 
			
		||||
			unreadCount: 0,
 | 
			
		||||
			date: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +76,7 @@ export default Vue.extend({
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		document.addEventListener('keydown', this.onKeydown);
 | 
			
		||||
		document.addEventListener('visibilitychange', this.onVisibilitychange, false);
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -87,10 +90,11 @@ export default Vue.extend({
 | 
			
		|||
		this.stream.dispose(this.connectionId);
 | 
			
		||||
 | 
			
		||||
		document.removeEventListener('keydown', this.onKeydown);
 | 
			
		||||
		document.removeEventListener('visibilitychange', this.onVisibilitychange);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch(cb?) {
 | 
			
		||||
		fetch() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +111,6 @@ export default Vue.extend({
 | 
			
		|||
					res(notes);
 | 
			
		||||
					this.fetching = false;
 | 
			
		||||
					this.$emit('loaded');
 | 
			
		||||
					if (cb) cb();
 | 
			
		||||
				}, rej);
 | 
			
		||||
			}));
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			@ -134,6 +137,11 @@ export default Vue.extend({
 | 
			
		|||
		},
 | 
			
		||||
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			if (document.hidden && note.userId !== (this as any).os.i.id) {
 | 
			
		||||
				this.unreadCount++;
 | 
			
		||||
				document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Prepend a note
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			@ -151,13 +159,20 @@ export default Vue.extend({
 | 
			
		|||
			this.fetch();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onVisibilitychange() {
 | 
			
		||||
			if (!document.hidden) {
 | 
			
		||||
				this.unreadCount = 0;
 | 
			
		||||
				document.title = 'Misskey';
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onKeydown(e) {
 | 
			
		||||
			if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
 | 
			
		||||
				if (e.which == 84) { // t
 | 
			
		||||
					this.focus();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
import ui from './ui.vue';
 | 
			
		||||
import timeline from './timeline.vue';
 | 
			
		||||
import note from './note.vue';
 | 
			
		||||
import notes from './notes.vue';
 | 
			
		||||
import mediaImage from './media-image.vue';
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +23,6 @@ import activity from './activity.vue';
 | 
			
		|||
import widgetContainer from './widget-container.vue';
 | 
			
		||||
 | 
			
		||||
Vue.component('mk-ui', ui);
 | 
			
		||||
Vue.component('mk-timeline', timeline);
 | 
			
		||||
Vue.component('mk-note', note);
 | 
			
		||||
Vue.component('mk-notes', notes);
 | 
			
		||||
Vue.component('mk-media-image', mediaImage);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,119 +0,0 @@
 | 
			
		|||
<template>
 | 
			
		||||
<div class="mk-timeline">
 | 
			
		||||
	<mk-friends-maker v-if="alone"/>
 | 
			
		||||
 | 
			
		||||
	<mk-notes ref="timeline" :more="existMore ? more : null">
 | 
			
		||||
		<div slot="empty">
 | 
			
		||||
			%fa:R comments%
 | 
			
		||||
			%i18n:@empty%
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-notes>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
const fetchLimit = 10;
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		date: {
 | 
			
		||||
			type: Date,
 | 
			
		||||
			required: false,
 | 
			
		||||
			default: null
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			moreFetching: false,
 | 
			
		||||
			existMore: false,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		alone(): boolean {
 | 
			
		||||
			return (this as any).os.i.followingCount == 0;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = (this as any).os.stream.getConnection();
 | 
			
		||||
		this.connectionId = (this as any).os.stream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('note', this.onNote);
 | 
			
		||||
		this.connection.on('follow', this.onChangeFollowing);
 | 
			
		||||
		this.connection.on('unfollow', this.onChangeFollowing);
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('note', this.onNote);
 | 
			
		||||
		this.connection.off('follow', this.onChangeFollowing);
 | 
			
		||||
		this.connection.off('unfollow', this.onChangeFollowing);
 | 
			
		||||
		(this as any).os.stream.dispose(this.connectionId);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch(cb?) {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 | 
			
		||||
				(this as any).api('notes/timeline', {
 | 
			
		||||
					limit: fetchLimit + 1,
 | 
			
		||||
					includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes,
 | 
			
		||||
					includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes
 | 
			
		||||
				}).then(notes => {
 | 
			
		||||
					if (notes.length == fetchLimit + 1) {
 | 
			
		||||
						notes.pop();
 | 
			
		||||
						this.existMore = true;
 | 
			
		||||
					}
 | 
			
		||||
					res(notes);
 | 
			
		||||
					this.fetching = false;
 | 
			
		||||
					this.$emit('loaded');
 | 
			
		||||
					if (cb) cb();
 | 
			
		||||
				}, rej);
 | 
			
		||||
			}));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		more() {
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
			(this as any).api('notes/timeline', {
 | 
			
		||||
				limit: fetchLimit + 1,
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id,
 | 
			
		||||
				includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes
 | 
			
		||||
			}).then(notes => {
 | 
			
		||||
				if (notes.length == fetchLimit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				notes.forEach(n => (this.$refs.timeline as any).append(n));
 | 
			
		||||
				this.moreFetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			// Prepend a note
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeFollowing() {
 | 
			
		||||
			this.fetch();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '~const.styl'
 | 
			
		||||
 | 
			
		||||
.mk-timeline
 | 
			
		||||
	> .mk-friends-maker
 | 
			
		||||
		margin-bottom 8px
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										196
									
								
								src/client/app/mobile/views/pages/dashboard.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/client/app/mobile/views/pages/dashboard.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,196 @@
 | 
			
		|||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header">%fa:home%ダッシュボード</span>
 | 
			
		||||
	<template slot="func">
 | 
			
		||||
		<button @click="customizing = !customizing">%fa:cog%</button>
 | 
			
		||||
	</template>
 | 
			
		||||
	<main>
 | 
			
		||||
		<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="slideshow">スライドショー</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><a @click="hint">カスタマイズのヒント</a></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 @click="widgetFunc(widget.id)">
 | 
			
		||||
						<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" :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" :ref="widget.id" :widget="widget" :is-mobile="true" @chosen="warp"/>
 | 
			
		||||
		</template>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDraggable
 | 
			
		||||
	},
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			showNav: false,
 | 
			
		||||
			widgets: [],
 | 
			
		||||
			customizing: false,
 | 
			
		||||
			widgetAdderSelected: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		if ((this as any).os.i.clientSettings.mobileHome == null) {
 | 
			
		||||
			Vue.set((this as any).os.i.clientSettings, 'mobileHome', [{
 | 
			
		||||
				name: 'calendar',
 | 
			
		||||
				id: 'a', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'activity',
 | 
			
		||||
				id: 'b', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'rss',
 | 
			
		||||
				id: 'c', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'photo-stream',
 | 
			
		||||
				id: 'd', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'donation',
 | 
			
		||||
				id: 'e', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'nav',
 | 
			
		||||
				id: 'f', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'version',
 | 
			
		||||
				id: 'g', data: {}
 | 
			
		||||
			}]);
 | 
			
		||||
			this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
			this.saveHome();
 | 
			
		||||
		} else {
 | 
			
		||||
			this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.$watch('os.i.clientSettings', i => {
 | 
			
		||||
			this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
		}, {
 | 
			
		||||
			deep: true
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = 'Misskey';
 | 
			
		||||
		document.documentElement.style.background = '#313a42';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		onHomeUpdated(data) {
 | 
			
		||||
			if (data.home) {
 | 
			
		||||
				(this as any).os.i.clientSettings.mobileHome = data.home;
 | 
			
		||||
				this.widgets = data.home;
 | 
			
		||||
			} else {
 | 
			
		||||
				const w = (this as any).os.i.clientSettings.mobileHome.find(w => w.id == data.id);
 | 
			
		||||
				if (w != null) {
 | 
			
		||||
					w.data = data.data;
 | 
			
		||||
					this.$refs[w.id][0].preventSave = true;
 | 
			
		||||
					this.$refs[w.id][0].props = w.data;
 | 
			
		||||
					this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		hint() {
 | 
			
		||||
			alert('ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。');
 | 
			
		||||
		},
 | 
			
		||||
		widgetFunc(id) {
 | 
			
		||||
			const w = this.$refs[id][0];
 | 
			
		||||
			if (w.func) w.func();
 | 
			
		||||
		},
 | 
			
		||||
		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).os.i.clientSettings.mobileHome = this.widgets;
 | 
			
		||||
			(this as any).api('i/update_mobile_home', {
 | 
			
		||||
				home: this.widgets
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
main
 | 
			
		||||
	margin 0 auto
 | 
			
		||||
	max-width 500px
 | 
			
		||||
 | 
			
		||||
	@media (min-width 500px)
 | 
			
		||||
		padding 8px
 | 
			
		||||
 | 
			
		||||
	> 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>
 | 
			
		||||
							
								
								
									
										166
									
								
								src/client/app/mobile/views/pages/home.timeline.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/client/app/mobile/views/pages/home.timeline.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,166 @@
 | 
			
		|||
<template>
 | 
			
		||||
<div>
 | 
			
		||||
	<mk-friends-maker v-if="src == 'home' && alone" style="margin-bottom:8px"/>
 | 
			
		||||
 | 
			
		||||
	<mk-notes ref="timeline" :more="existMore ? more : null">
 | 
			
		||||
		<div slot="empty">
 | 
			
		||||
			%fa:R comments%
 | 
			
		||||
			%i18n:@empty%
 | 
			
		||||
		</div>
 | 
			
		||||
	</mk-notes>
 | 
			
		||||
</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
 | 
			
		||||
const fetchLimit = 10;
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	props: {
 | 
			
		||||
		src: {
 | 
			
		||||
			type: String,
 | 
			
		||||
			required: true
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			fetching: true,
 | 
			
		||||
			moreFetching: false,
 | 
			
		||||
			existMore: false,
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null,
 | 
			
		||||
			unreadCount: 0,
 | 
			
		||||
			date: null
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		alone(): boolean {
 | 
			
		||||
			return (this as any).os.i.followingCount == 0;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		stream(): any {
 | 
			
		||||
			return this.src == 'home'
 | 
			
		||||
				? (this as any).os.stream
 | 
			
		||||
				: this.src == 'local'
 | 
			
		||||
					? (this as any).os.streams.localTimelineStream
 | 
			
		||||
					: (this as any).os.streams.globalTimelineStream;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		endpoint(): string {
 | 
			
		||||
			return this.src == 'home'
 | 
			
		||||
				? 'notes/timeline'
 | 
			
		||||
				: this.src == 'local'
 | 
			
		||||
					? 'notes/local-timeline'
 | 
			
		||||
					: 'notes/global-timeline';
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		canFetchMore(): boolean {
 | 
			
		||||
			return !this.moreFetching && !this.fetching && this.existMore;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.connection = this.stream.getConnection();
 | 
			
		||||
		this.connectionId = this.stream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('note', this.onNote);
 | 
			
		||||
		if (this.src == 'home') {
 | 
			
		||||
			this.connection.on('follow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.on('unfollow', this.onChangeFollowing);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		document.addEventListener('visibilitychange', this.onVisibilitychange, false);
 | 
			
		||||
 | 
			
		||||
		this.fetch();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('note', this.onNote);
 | 
			
		||||
		if (this.src == 'home') {
 | 
			
		||||
			this.connection.off('follow', this.onChangeFollowing);
 | 
			
		||||
			this.connection.off('unfollow', this.onChangeFollowing);
 | 
			
		||||
		}
 | 
			
		||||
		this.stream.dispose(this.connectionId);
 | 
			
		||||
 | 
			
		||||
		document.removeEventListener('visibilitychange', this.onVisibilitychange);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fetch() {
 | 
			
		||||
			this.fetching = true;
 | 
			
		||||
 | 
			
		||||
			(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
 | 
			
		||||
				(this as any).api(this.endpoint, {
 | 
			
		||||
					limit: fetchLimit + 1,
 | 
			
		||||
					untilDate: this.date ? this.date.getTime() : undefined,
 | 
			
		||||
					includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes,
 | 
			
		||||
					includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes
 | 
			
		||||
				}).then(notes => {
 | 
			
		||||
					if (notes.length == fetchLimit + 1) {
 | 
			
		||||
						notes.pop();
 | 
			
		||||
						this.existMore = true;
 | 
			
		||||
					}
 | 
			
		||||
					res(notes);
 | 
			
		||||
					this.fetching = false;
 | 
			
		||||
					this.$emit('loaded');
 | 
			
		||||
				}, rej);
 | 
			
		||||
			}));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		more() {
 | 
			
		||||
			if (!this.canFetchMore) return;
 | 
			
		||||
 | 
			
		||||
			this.moreFetching = true;
 | 
			
		||||
 | 
			
		||||
			(this as any).api(this.endpoint, {
 | 
			
		||||
				limit: fetchLimit + 1,
 | 
			
		||||
				untilId: (this.$refs.timeline as any).tail().id,
 | 
			
		||||
				includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes,
 | 
			
		||||
				includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes
 | 
			
		||||
			}).then(notes => {
 | 
			
		||||
				if (notes.length == fetchLimit + 1) {
 | 
			
		||||
					notes.pop();
 | 
			
		||||
				} else {
 | 
			
		||||
					this.existMore = false;
 | 
			
		||||
				}
 | 
			
		||||
				notes.forEach(n => (this.$refs.timeline as any).append(n));
 | 
			
		||||
				this.moreFetching = false;
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onNote(note) {
 | 
			
		||||
			if (document.hidden && note.userId !== (this as any).os.i.id) {
 | 
			
		||||
				this.unreadCount++;
 | 
			
		||||
				document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Prepend a note
 | 
			
		||||
			(this.$refs.timeline as any).prepend(note);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onChangeFollowing() {
 | 
			
		||||
			this.fetch();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		focus() {
 | 
			
		||||
			(this.$refs.timeline as any).focus();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		warp(date) {
 | 
			
		||||
			this.date = date;
 | 
			
		||||
			this.fetch();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onVisibilitychange() {
 | 
			
		||||
			if (!document.hidden) {
 | 
			
		||||
				this.unreadCount = 0;
 | 
			
		||||
				document.title = 'Misskey';
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,59 +1,37 @@
 | 
			
		|||
<template>
 | 
			
		||||
<mk-ui>
 | 
			
		||||
	<span slot="header" @click="showTl = !showTl">
 | 
			
		||||
		<template v-if="showTl">%fa:home%%i18n:@timeline%</template>
 | 
			
		||||
		<template v-else>%fa:home%ウィジェット</template>
 | 
			
		||||
	<span slot="header" @click="showNav = true">
 | 
			
		||||
		<span>
 | 
			
		||||
			<span v-if="src == 'home'">%fa:home%ホーム</span>
 | 
			
		||||
			<span v-if="src == 'local'">%fa:R comments%ローカル</span>
 | 
			
		||||
			<span v-if="src == 'global'">%fa:globe%グローバル</span>
 | 
			
		||||
			<span v-if="src == 'list'">%fa:list%{{ list.title }}</span>
 | 
			
		||||
		</span>
 | 
			
		||||
		<span style="margin-left:8px">
 | 
			
		||||
			<template v-if="showTl">%fa:angle-down%</template>
 | 
			
		||||
			<template v-if="!showNav">%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>
 | 
			
		||||
		<button @click="fn">%fa:pencil-alt%</button>
 | 
			
		||||
	</template>
 | 
			
		||||
 | 
			
		||||
	<main>
 | 
			
		||||
		<div class="tl">
 | 
			
		||||
			<mk-timeline @loaded="onLoaded" v-show="showTl"/>
 | 
			
		||||
		<div class="nav" v-if="showNav">
 | 
			
		||||
			<div class="bg" @click="showNav = false"></div>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<span :data-is-active="src == 'home'" @click="src = 'home'">%fa:home% ホーム</span>
 | 
			
		||||
				<span :data-is-active="src == 'local'" @click="src = 'local'">%fa:R comments% ローカル</span>
 | 
			
		||||
				<span :data-is-active="src == 'global'" @click="src = 'global'">%fa:globe% グローバル</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="widgets" v-show="!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="slideshow">スライドショー</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><a @click="hint">カスタマイズのヒント</a></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 @click="widgetFunc(widget.id)">
 | 
			
		||||
							<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" :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" :ref="widget.id" :widget="widget" :is-mobile="true" @chosen="warp"/>
 | 
			
		||||
			</template>
 | 
			
		||||
 | 
			
		||||
		<div class="tl">
 | 
			
		||||
			<x-tl v-if="src == 'home'" ref="tl" key="home" src="home" @loaded="onLoaded"/>
 | 
			
		||||
			<x-tl v-if="src == 'local'" ref="tl" key="local" src="local"/>
 | 
			
		||||
			<x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
 | 
			
		||||
			<mk-user-list-timeline v-if="src == 'list'" ref="tl" key="list" :list="list"/>
 | 
			
		||||
		</div>
 | 
			
		||||
	</main>
 | 
			
		||||
</mk-ui>
 | 
			
		||||
| 
						 | 
				
			
			@ -61,144 +39,38 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import * as XDraggable from 'vuedraggable';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import Progress from '../../../common/scripts/loading';
 | 
			
		||||
import getNoteSummary from '../../../../../renderers/get-note-summary';
 | 
			
		||||
import XTl from './home.timeline.vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	components: {
 | 
			
		||||
		XDraggable
 | 
			
		||||
		XTl
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			connection: null,
 | 
			
		||||
			connectionId: null,
 | 
			
		||||
			unreadCount: 0,
 | 
			
		||||
			showTl: true,
 | 
			
		||||
			widgets: [],
 | 
			
		||||
			customizing: false,
 | 
			
		||||
			widgetAdderSelected: null
 | 
			
		||||
			src: 'home',
 | 
			
		||||
			list: null,
 | 
			
		||||
			showNav: false
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
	created() {
 | 
			
		||||
		if ((this as any).os.i.clientSettings.mobileHome == null) {
 | 
			
		||||
			Vue.set((this as any).os.i.clientSettings, 'mobileHome', [{
 | 
			
		||||
				name: 'calendar',
 | 
			
		||||
				id: 'a', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'activity',
 | 
			
		||||
				id: 'b', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'rss',
 | 
			
		||||
				id: 'c', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'photo-stream',
 | 
			
		||||
				id: 'd', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'donation',
 | 
			
		||||
				id: 'e', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'nav',
 | 
			
		||||
				id: 'f', data: {}
 | 
			
		||||
			}, {
 | 
			
		||||
				name: 'version',
 | 
			
		||||
				id: 'g', data: {}
 | 
			
		||||
			}]);
 | 
			
		||||
			this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
			this.saveHome();
 | 
			
		||||
		} else {
 | 
			
		||||
			this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.$watch('os.i.clientSettings', i => {
 | 
			
		||||
			this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
		}, {
 | 
			
		||||
			deep: true
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	mounted() {
 | 
			
		||||
		document.title = 'Misskey';
 | 
			
		||||
		document.documentElement.style.background = '#313a42';
 | 
			
		||||
 | 
			
		||||
		this.connection = (this as any).os.stream.getConnection();
 | 
			
		||||
		this.connectionId = (this as any).os.stream.use();
 | 
			
		||||
 | 
			
		||||
		this.connection.on('note', this.onStreamNote);
 | 
			
		||||
		this.connection.on('mobile_home_updated', this.onHomeUpdated);
 | 
			
		||||
		document.addEventListener('visibilitychange', this.onVisibilitychange, false);
 | 
			
		||||
 | 
			
		||||
		Progress.start();
 | 
			
		||||
	},
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.connection.off('note', this.onStreamNote);
 | 
			
		||||
		this.connection.off('mobile_home_updated', this.onHomeUpdated);
 | 
			
		||||
		(this as any).os.stream.dispose(this.connectionId);
 | 
			
		||||
		document.removeEventListener('visibilitychange', this.onVisibilitychange);
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		fn() {
 | 
			
		||||
			(this as any).apis.post();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		onLoaded() {
 | 
			
		||||
			Progress.done();
 | 
			
		||||
		},
 | 
			
		||||
		onStreamNote(note) {
 | 
			
		||||
			if (document.hidden && note.userId !== (this as any).os.i.id) {
 | 
			
		||||
				this.unreadCount++;
 | 
			
		||||
				document.title = `(${this.unreadCount}) ${getNoteSummary(note)}`;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onVisibilitychange() {
 | 
			
		||||
			if (!document.hidden) {
 | 
			
		||||
				this.unreadCount = 0;
 | 
			
		||||
				document.title = 'Misskey';
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		onHomeUpdated(data) {
 | 
			
		||||
			if (data.home) {
 | 
			
		||||
				(this as any).os.i.clientSettings.mobileHome = data.home;
 | 
			
		||||
				this.widgets = data.home;
 | 
			
		||||
			} else {
 | 
			
		||||
				const w = (this as any).os.i.clientSettings.mobileHome.find(w => w.id == data.id);
 | 
			
		||||
				if (w != null) {
 | 
			
		||||
					w.data = data.data;
 | 
			
		||||
					this.$refs[w.id][0].preventSave = true;
 | 
			
		||||
					this.$refs[w.id][0].props = w.data;
 | 
			
		||||
					this.widgets = (this as any).os.i.clientSettings.mobileHome;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		hint() {
 | 
			
		||||
			alert('ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。');
 | 
			
		||||
		},
 | 
			
		||||
		widgetFunc(id) {
 | 
			
		||||
			const w = this.$refs[id][0];
 | 
			
		||||
			if (w.func) w.func();
 | 
			
		||||
		},
 | 
			
		||||
		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).os.i.clientSettings.mobileHome = this.widgets;
 | 
			
		||||
			(this as any).api('i/update_mobile_home', {
 | 
			
		||||
				home: this.widgets
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
		warp() {
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -208,52 +80,30 @@ export default Vue.extend({
 | 
			
		|||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
main
 | 
			
		||||
	> .nav
 | 
			
		||||
		> .bg
 | 
			
		||||
			position fixed
 | 
			
		||||
			z-index 10000
 | 
			
		||||
			top 0
 | 
			
		||||
			left 0
 | 
			
		||||
			width 100%
 | 
			
		||||
			height 100%
 | 
			
		||||
			background rgba(#000, 0.5)
 | 
			
		||||
 | 
			
		||||
		> .body
 | 
			
		||||
			position fixed
 | 
			
		||||
			z-index 10001
 | 
			
		||||
			top 48px
 | 
			
		||||
			left 0
 | 
			
		||||
			background #fff
 | 
			
		||||
			border-radius 8px
 | 
			
		||||
 | 
			
		||||
	> .tl
 | 
			
		||||
		> .mk-timeline
 | 
			
		||||
			max-width 600px
 | 
			
		||||
			margin 0 auto
 | 
			
		||||
			padding 8px
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				padding 16px
 | 
			
		||||
 | 
			
		||||
	> .widgets
 | 
			
		||||
		max-width 600px
 | 
			
		||||
		margin 0 auto
 | 
			
		||||
		max-width 500px
 | 
			
		||||
		padding 8px
 | 
			
		||||
 | 
			
		||||
		@media (min-width 500px)
 | 
			
		||||
			padding 8px
 | 
			
		||||
 | 
			
		||||
		> 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
 | 
			
		||||
			padding 16px
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue