From 7a97924d013ad0fbea6c4b20a478651320c836a0 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 22 Feb 2018 07:06:47 +0900
Subject: [PATCH] wip

---
 src/web/app/common/views/components/index.ts  |   2 +
 .../views/components/messaging-room.vue       |   4 +-
 .../app/common/views/components/post-menu.vue |  59 ++++-----
 .../desktop/views/components/posts.post.vue   |   5 +-
 .../views/components/drive-file-chooser.vue   |   4 +-
 .../mobile/views/components/drive.folder.vue  |   4 +-
 src/web/app/mobile/views/components/drive.vue |  25 ++--
 src/web/app/mobile/views/components/index.ts  |   2 +
 .../app/mobile/views/components/post-form.vue |  73 +++++++----
 .../views/components/posts-post-sub.vue       | 117 ------------------
 .../views/components/posts.post.sub.vue       | 108 ++++++++++++++++
 .../mobile/views/components/posts.post.vue    |  34 +++++
 .../app/mobile/views/components/timeline.vue  |   2 +-
 .../app/mobile/views/components/ui.header.vue |   1 -
 .../app/mobile/views/components/ui.nav.vue    |  20 +--
 15 files changed, 265 insertions(+), 195 deletions(-)
 delete mode 100644 src/web/app/mobile/views/components/posts-post-sub.vue
 create mode 100644 src/web/app/mobile/views/components/posts.post.sub.vue

diff --git a/src/web/app/common/views/components/index.ts b/src/web/app/common/views/components/index.ts
index bde313910..d3f6a425f 100644
--- a/src/web/app/common/views/components/index.ts
+++ b/src/web/app/common/views/components/index.ts
@@ -18,6 +18,7 @@ import messaging from './messaging.vue';
 import messagingRoom from './messaging-room.vue';
 import urlPreview from './url-preview.vue';
 import twitterSetting from './twitter-setting.vue';
+import fileTypeIcon from './file-type-icon.vue';
 
 Vue.component('mk-signin', signin);
 Vue.component('mk-signup', signup);
@@ -37,3 +38,4 @@ Vue.component('mk-messaging', messaging);
 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);
diff --git a/src/web/app/common/views/components/messaging-room.vue b/src/web/app/common/views/components/messaging-room.vue
index 5022655a2..cfb1e23ac 100644
--- a/src/web/app/common/views/components/messaging-room.vue
+++ b/src/web/app/common/views/components/messaging-room.vue
@@ -15,7 +15,7 @@
 		</template>
 	</div>
 	<footer>
-		<div ref="notifications"></div>
+		<div ref="notifications" class="notifications"></div>
 		<div class="grippie" title="%i18n:common.tags.mk-messaging-room.resize-form%"></div>
 		<x-form :user="user"/>
 	</footer>
@@ -278,7 +278,7 @@ export default Vue.extend({
 		background rgba(255, 255, 255, 0.95)
 		background-clip content-box
 
-		> [ref='notifications']
+		> .notifications
 			position absolute
 			top -48px
 			width 100%
diff --git a/src/web/app/common/views/components/post-menu.vue b/src/web/app/common/views/components/post-menu.vue
index e14d67fc8..a53680e55 100644
--- a/src/web/app/common/views/components/post-menu.vue
+++ b/src/web/app/common/views/components/post-menu.vue
@@ -1,8 +1,8 @@
 <template>
 <div class="mk-post-menu">
 	<div class="backdrop" ref="backdrop" @click="close"></div>
-	<div class="popover { compact: opts.compact }" ref="popover">
-		<button v-if="post.user_id === I.id" @click="pin">%i18n:common.tags.mk-post-menu.pin%</button>
+	<div class="popover" :class="{ compact }" ref="popover">
+		<button v-if="post.user_id == os.i.id" @click="pin">%i18n:common.tags.mk-post-menu.pin%</button>
 	</div>
 </div>
 </template>
@@ -14,36 +14,38 @@ import * as anime from 'animejs';
 export default Vue.extend({
 	props: ['post', 'source', 'compact'],
 	mounted() {
-		const popover = this.$refs.popover as any;
+		this.$nextTick(() => {
+			const popover = this.$refs.popover as any;
 
-		const rect = this.source.getBoundingClientRect();
-		const width = popover.offsetWidth;
-		const height = popover.offsetHeight;
+			const rect = this.source.getBoundingClientRect();
+			const width = popover.offsetWidth;
+			const height = popover.offsetHeight;
 
-		if (this.compact) {
-			const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
-			const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
-			popover.style.left = (x - (width / 2)) + 'px';
-			popover.style.top = (y - (height / 2)) + 'px';
-		} else {
-			const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
-			const y = rect.top + window.pageYOffset + this.source.offsetHeight;
-			popover.style.left = (x - (width / 2)) + 'px';
-			popover.style.top = y + 'px';
-		}
+			if (this.compact) {
+				const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
+				const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2);
+				popover.style.left = (x - (width / 2)) + 'px';
+				popover.style.top = (y - (height / 2)) + 'px';
+			} else {
+				const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2);
+				const y = rect.top + window.pageYOffset + this.source.offsetHeight;
+				popover.style.left = (x - (width / 2)) + 'px';
+				popover.style.top = y + 'px';
+			}
 
-		anime({
-			targets: this.$refs.backdrop,
-			opacity: 1,
-			duration: 100,
-			easing: 'linear'
-		});
+			anime({
+				targets: this.$refs.backdrop,
+				opacity: 1,
+				duration: 100,
+				easing: 'linear'
+			});
 
-		anime({
-			targets: this.$refs.popover,
-			opacity: 1,
-			scale: [0.5, 1],
-			duration: 500
+			anime({
+				targets: this.$refs.popover,
+				opacity: 1,
+				scale: [0.5, 1],
+				duration: 500
+			});
 		});
 	},
 	methods: {
@@ -134,5 +136,6 @@ $border-color = rgba(27, 31, 35, 0.15)
 
 		> button
 			display block
+			padding 16px
 
 </style>
diff --git a/src/web/app/desktop/views/components/posts.post.vue b/src/web/app/desktop/views/components/posts.post.vue
index 92218ead3..c757cbc7f 100644
--- a/src/web/app/desktop/views/components/posts.post.vue
+++ b/src/web/app/desktop/views/components/posts.post.vue
@@ -8,7 +8,10 @@
 			<router-link class="avatar-anchor" :to="`/${post.user.username}`" v-user-preview="post.user_id">
 				<img class="avatar" :src="`${post.user.avatar_url}?thumbnail&size=32`" alt="avatar"/>
 			</router-link>
-			%fa:retweet%{{'%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{'))}}<a class="name" :href="`/${post.user.username}`" v-user-preview="post.user_id">{{ post.user.name }}</a>{{'%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1)}}
+			%fa:retweet%
+			{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr(0, '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('{')) }}
+			<a class="name" :href="`/${post.user.username}`" v-user-preview="post.user_id">{{ post.user.name }}</a>
+			{{ '%i18n:desktop.tags.mk-timeline-post.reposted-by%'.substr('%i18n:desktop.tags.mk-timeline-post.reposted-by%'.indexOf('}') + 1) }}
 		</p>
 		<mk-time :time="post.created_at"/>
 	</div>
diff --git a/src/web/app/mobile/views/components/drive-file-chooser.vue b/src/web/app/mobile/views/components/drive-file-chooser.vue
index 6f1d25f63..6806af0f1 100644
--- a/src/web/app/mobile/views/components/drive-file-chooser.vue
+++ b/src/web/app/mobile/views/components/drive-file-chooser.vue
@@ -4,10 +4,10 @@
 		<header>
 			<h1>%i18n:mobile.tags.mk-drive-selector.select-file%<span class="count" v-if="files.length > 0">({{ files.length }})</span></h1>
 			<button class="close" @click="cancel">%fa:times%</button>
-			<button v-if="opts.multiple" class="ok" @click="ok">%fa:check%</button>
+			<button v-if="multiple" class="ok" @click="ok">%fa:check%</button>
 		</header>
 		<mk-drive ref="browser"
-			select-file
+			:select-file="true"
 			:multiple="multiple"
 			@change-selection="onChangeSelection"
 			@selected="onSelected"
diff --git a/src/web/app/mobile/views/components/drive.folder.vue b/src/web/app/mobile/views/components/drive.folder.vue
index b776af7aa..22ff38fec 100644
--- a/src/web/app/mobile/views/components/drive.folder.vue
+++ b/src/web/app/mobile/views/components/drive.folder.vue
@@ -1,5 +1,5 @@
 <template>
-<a class="folder" @click.prevent="onClick" :href="`/i/drive/folder/${ folder.id }`">
+<a class="root folder" @click.prevent="onClick" :href="`/i/drive/folder/${ folder.id }`">
 	<div class="container">
 		<p class="name">%fa:folder%{{ folder.name }}</p>%fa:angle-right%
 	</div>
@@ -24,7 +24,7 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-.folder
+.root.folder
 	display block
 	color #777
 	text-decoration none !important
diff --git a/src/web/app/mobile/views/components/drive.vue b/src/web/app/mobile/views/components/drive.vue
index f334f2241..35d91d183 100644
--- a/src/web/app/mobile/views/components/drive.vue
+++ b/src/web/app/mobile/views/components/drive.vue
@@ -26,11 +26,11 @@
 			</p>
 		</div>
 		<div class="folders" v-if="folders.length > 0">
-			<mk-drive-folder v-for="folder in folders" :key="folder.id" :folder="folder"/>
+			<x-folder v-for="folder in folders" :key="folder.id" :folder="folder"/>
 			<p v-if="moreFolders">%i18n:mobile.tags.mk-drive.load-more%</p>
 		</div>
 		<div class="files" v-if="files.length > 0">
-			<mk-drive-file v-for="file in files" :key="file.id" :file="file"/>
+			<x-file v-for="file in files" :key="file.id" :file="file"/>
 			<button class="more" v-if="moreFiles" @click="fetchMoreFiles">
 				{{ fetchingMoreFiles ? '%i18n:common.loading%' : '%i18n:mobile.tags.mk-drive.load-more%' }}
 			</button>
@@ -46,15 +46,23 @@
 			<div class="dot2"></div>
 		</div>
 	</div>
-	<input ref="file" type="file" multiple="multiple" @change="onChangeLocalFile"/>
-	<mk-drive-file-detail v-if="file != null" :file="file"/>
+	<input ref="file" class="file" type="file" multiple="multiple" @change="onChangeLocalFile"/>
+	<x-file-detail v-if="file != null" :file="file"/>
 </div>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
+import XFolder from './drive.folder.vue';
+import XFile from './drive.file.vue';
+import XFileDetail from './drive.file-detail.vue';
 
 export default Vue.extend({
+	components: {
+		XFolder,
+		XFile,
+		XFileDetail
+	},
 	props: ['initFolder', 'initFile', 'selectFile', 'multiple', 'isNaked', 'top'],
 	data() {
 		return {
@@ -423,8 +431,7 @@ export default Vue.extend({
 				alert('現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。');
 				return;
 			}
-			const dialog = riot.mount(document.body.appendChild(document.createElement('mk-drive-folder-selector')))[0];
-			dialog.one('selected', folder => {
+			(this as any).apis.chooseDriveFolder().then(folder => {
 				(this as any).api('drive/folders/update', {
 					parent_id: folder ? folder.id : null,
 					folder_id: this.folder.id
@@ -510,11 +517,11 @@ export default Vue.extend({
 				color #777
 
 		> .folders
-			> .mk-drive-folder
+			> .folder
 				border-bottom solid 1px #eee
 
 		> .files
-			> .mk-drive-file
+			> .file
 				border-bottom solid 1px #eee
 
 			> .more
@@ -568,7 +575,7 @@ export default Vue.extend({
 			}
 		}
 
-	> [ref='file']
+	> .file
 		display none
 
 </style>
diff --git a/src/web/app/mobile/views/components/index.ts b/src/web/app/mobile/views/components/index.ts
index c90275d68..715e291a7 100644
--- a/src/web/app/mobile/views/components/index.ts
+++ b/src/web/app/mobile/views/components/index.ts
@@ -5,9 +5,11 @@ import home from './home.vue';
 import timeline from './timeline.vue';
 import posts from './posts.vue';
 import imagesImage from './images-image.vue';
+import drive from './drive.vue';
 
 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);
+Vue.component('mk-drive', drive);
diff --git a/src/web/app/mobile/views/components/post-form.vue b/src/web/app/mobile/views/components/post-form.vue
index bba669229..3e8206c92 100644
--- a/src/web/app/mobile/views/components/post-form.vue
+++ b/src/web/app/mobile/views/components/post-form.vue
@@ -3,37 +3,40 @@
 	<header>
 		<button class="cancel" @click="cancel">%fa:times%</button>
 		<div>
-			<span v-if="refs.text" class="text-count" :class="{ over: refs.text.value.length > 1000 }">{{ 1000 - refs.text.value.length }}</span>
-			<button class="submit" @click="post">%i18n:mobile.tags.mk-post-form.submit%</button>
+			<span class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</span>
+			<button class="submit" :disabled="posting" @click="post">%i18n:mobile.tags.mk-post-form.submit%</button>
 		</div>
 	</header>
 	<div class="form">
 		<mk-post-preview v-if="reply" :post="reply"/>
-		<textarea v-model="text" :disabled="wait" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.post-placeholder%'"></textarea>
+		<textarea v-model="text" ref="text" :disabled="posting" :placeholder="reply ? '%i18n:mobile.tags.mk-post-form.reply-placeholder%' : '%i18n:mobile.tags.mk-post-form.post-placeholder%'"></textarea>
 		<div class="attaches" v-show="files.length != 0">
-			<ul class="files" ref="attaches">
-				<li class="file" v-for="file in files">
-					<div class="img" :style="`background-image: url(${file.url}?thumbnail&size=128)`" @click="removeFile(file)"></div>
-				</li>
-			</ul>
+			<x-draggable class="files" :list="files" :options="{ animation: 150 }">
+				<div class="file" v-for="file in files" :key="file.id">
+					<div class="img" :style="`background-image: url(${file.url}?thumbnail&size=128)`" @click="detachMedia(file)"></div>
+				</div>
+			</x-draggable>
 		</div>
 		<mk-poll-editor v-if="poll" ref="poll"/>
-		<mk-uploader @uploaded="attachMedia" @change="onChangeUploadings"/>
-		<button ref="upload" @click="selectFile">%fa:upload%</button>
-		<button ref="drive" @click="selectFileFromDrive">%fa:cloud%</button>
+		<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
+		<button class="upload" @click="chooseFile">%fa:upload%</button>
+		<button class="drive" @click="chooseFileFromDrive">%fa:cloud%</button>
 		<button class="kao" @click="kao">%fa:R smile%</button>
-		<button class="poll" @click="addPoll">%fa:chart-pie%</button>
-		<input ref="file" type="file" accept="image/*" multiple="multiple" @change="onChangeFile"/>
+		<button class="poll" @click="poll = true">%fa:chart-pie%</button>
+		<input ref="file" class="file" type="file" accept="image/*" multiple="multiple" @change="onChangeFile"/>
 	</div>
 </div>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
-import Sortable from 'sortablejs';
+import * as XDraggable from 'vuedraggable';
 import getKao from '../../../common/scripts/get-kao';
 
 export default Vue.extend({
+	components: {
+		XDraggable
+	},
 	props: ['reply'],
 	data() {
 		return {
@@ -45,19 +48,27 @@ export default Vue.extend({
 		};
 	},
 	mounted() {
-		(this.$refs.text as any).focus();
-
-		new Sortable(this.$refs.attaches, {
-			animation: 150
+		this.$nextTick(() => {
+			(this.$refs.text as any).focus();
 		});
 	},
 	methods: {
+		chooseFile() {
+			(this.$refs.file as any).click();
+		},
+		chooseFileFromDrive() {
+			(this as any).apis.chooseDriveFile({
+				multiple: true
+			}).then(files => {
+				files.forEach(this.attachMedia);
+			});
+		},
 		attachMedia(driveFile) {
 			this.files.push(driveFile);
 			this.$emit('change-attached-media', this.files);
 		},
-		detachMedia(id) {
-			this.files = this.files.filter(x => x.id != id);
+		detachMedia(file) {
+			this.files = this.files.filter(x => x.id != file.id);
 			this.$emit('change-attached-media', this.files);
 		},
 		onChangeFile() {
@@ -75,6 +86,20 @@ export default Vue.extend({
 			this.poll = false;
 			this.$emit('change-attached-media');
 		},
+		post() {
+			this.posting = true;
+			(this as any).api('posts/create', {
+				text: this.text == '' ? undefined : this.text,
+				media_ids: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
+				reply_id: this.reply ? this.reply.id : undefined,
+				poll: this.poll ? (this.$refs.poll as any).get() : undefined
+			}).then(data => {
+				this.$emit('post');
+				this.$destroy();
+			}).catch(err => {
+				this.posting = false;
+			});
+		},
 		cancel() {
 			this.$emit('cancel');
 			this.$destroy();
@@ -167,10 +192,10 @@ export default Vue.extend({
 			margin 8px 0 0 0
 			padding 8px
 
-		> [ref='file']
+		> .file
 			display none
 
-		> [ref='text']
+		> textarea
 			display block
 			padding 12px
 			margin 0
@@ -187,8 +212,8 @@ export default Vue.extend({
 			&:disabled
 				opacity 0.5
 
-		> [ref='upload']
-		> [ref='drive']
+		> .upload
+		> .drive
 		.kao
 		.poll
 			display inline-block
diff --git a/src/web/app/mobile/views/components/posts-post-sub.vue b/src/web/app/mobile/views/components/posts-post-sub.vue
deleted file mode 100644
index 421d51b92..000000000
--- a/src/web/app/mobile/views/components/posts-post-sub.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-<template>
-<div class="mk-posts-post-sub">
-	<article>
-		<a class="avatar-anchor" href={ '/' + post.user.username }>
-			<img class="avatar" src={ post.user.avatar_url + '?thumbnail&size=96' } alt="avatar"/>
-		</a>
-		<div class="main">
-			<header>
-				<a class="name" href={ '/' + post.user.username }>{ post.user.name }</a>
-				<span class="username">@{ post.user.username }</span>
-				<a class="created-at" href={ '/' + post.user.username + '/' + post.id }>
-					<mk-time time={ post.created_at }/>
-				</a>
-			</header>
-			<div class="body">
-				<mk-sub-post-content class="text" post={ post }/>
-			</div>
-		</div>
-	</article>
-</div>
-</template>
-
-<script lang="ts">
-import Vue from 'vue';
-export default Vue.extend({
-	props: ['post']
-});
-</script>
-
-
-<style lang="stylus" scoped>
-.mk-posts-post-sub
-	font-size 0.9em
-
-	> article
-		padding 16px
-
-		&:after
-			content ""
-			display block
-			clear both
-
-		&:hover
-			> .main > footer > button
-				color #888
-
-		> .avatar-anchor
-			display block
-			float left
-			margin 0 10px 0 0
-
-			@media (min-width 500px)
-				margin-right 16px
-
-			> .avatar
-				display block
-				width 44px
-				height 44px
-				margin 0
-				border-radius 8px
-				vertical-align bottom
-
-				@media (min-width 500px)
-					width 52px
-					height 52px
-
-		> .main
-			float left
-			width calc(100% - 54px)
-
-			@media (min-width 500px)
-				width calc(100% - 68px)
-
-			> header
-				display flex
-				margin-bottom 2px
-				white-space nowrap
-
-				> .name
-					display block
-					margin 0 0.5em 0 0
-					padding 0
-					overflow hidden
-					color #607073
-					font-size 1em
-					font-weight 700
-					text-align left
-					text-decoration none
-					text-overflow ellipsis
-
-					&:hover
-						text-decoration underline
-
-				> .username
-					text-align left
-					margin 0
-					color #d1d8da
-
-				> .created-at
-					margin-left auto
-					color #b2b8bb
-
-			> .body
-
-				> .text
-					cursor default
-					margin 0
-					padding 0
-					font-size 1.1em
-					color #717171
-
-					pre
-						max-height 120px
-						font-size 80%
-
-</style>
-
diff --git a/src/web/app/mobile/views/components/posts.post.sub.vue b/src/web/app/mobile/views/components/posts.post.sub.vue
new file mode 100644
index 000000000..5bb6444a6
--- /dev/null
+++ b/src/web/app/mobile/views/components/posts.post.sub.vue
@@ -0,0 +1,108 @@
+<template>
+<div class="sub">
+	<router-link class="avatar-anchor" :to="`/${post.user.username}`">
+		<img class="avatar" :src="`${post.user.avatar_url}?thumbnail&size=96`" alt="avatar"/>
+	</router-link>
+	<div class="main">
+		<header>
+			<router-link class="name" :to="`/${post.user.username}`">{{ post.user.name }}</router-link>
+			<span class="username">@{{ post.user.username }}</span>
+			<router-link class="created-at" :href="`/${post.user.username}/${post.id}`">
+				<mk-time :time="post.created_at"/>
+			</router-link>
+		</header>
+		<div class="body">
+			<mk-sub-post-content class="text" :post="post"/>
+		</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	props: ['post']
+});
+</script>
+
+<style lang="stylus" scoped>
+.sub
+	font-size 0.9em
+	padding 16px
+
+	&:after
+		content ""
+		display block
+		clear both
+
+	> .avatar-anchor
+		display block
+		float left
+		margin 0 10px 0 0
+
+		@media (min-width 500px)
+			margin-right 16px
+
+		> .avatar
+			display block
+			width 44px
+			height 44px
+			margin 0
+			border-radius 8px
+			vertical-align bottom
+
+			@media (min-width 500px)
+				width 52px
+				height 52px
+
+	> .main
+		float left
+		width calc(100% - 54px)
+
+		@media (min-width 500px)
+			width calc(100% - 68px)
+
+		> header
+			display flex
+			margin-bottom 2px
+			white-space nowrap
+
+			> .name
+				display block
+				margin 0 0.5em 0 0
+				padding 0
+				overflow hidden
+				color #607073
+				font-size 1em
+				font-weight 700
+				text-align left
+				text-decoration none
+				text-overflow ellipsis
+
+				&:hover
+					text-decoration underline
+
+			> .username
+				text-align left
+				margin 0
+				color #d1d8da
+
+			> .created-at
+				margin-left auto
+				color #b2b8bb
+
+		> .body
+
+			> .text
+				cursor default
+				margin 0
+				padding 0
+				font-size 1.1em
+				color #717171
+
+				pre
+					max-height 120px
+					font-size 80%
+
+</style>
+
diff --git a/src/web/app/mobile/views/components/posts.post.vue b/src/web/app/mobile/views/components/posts.post.vue
index 225a530b5..9a7d633d4 100644
--- a/src/web/app/mobile/views/components/posts.post.vue
+++ b/src/web/app/mobile/views/components/posts.post.vue
@@ -69,8 +69,14 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import MkPostMenu from '../../../common/views/components/post-menu.vue';
+import MkReactionPicker from '../../../common/views/components/reaction-picker.vue';
+import XSub from './posts.post.sub.vue';
 
 export default Vue.extend({
+	components: {
+		XSub
+	},
 	props: ['post'],
 	data() {
 		return {
@@ -152,6 +158,34 @@ export default Vue.extend({
 				this.$emit('update:post', post);
 			}
 		},
+		reply() {
+			(this as any).apis.post({
+				reply: this.p
+			});
+		},
+		repost() {
+			(this as any).apis.post({
+				repost: this.p
+			});
+		},
+		react() {
+			document.body.appendChild(new MkReactionPicker({
+				propsData: {
+					source: this.$refs.reactButton,
+					post: this.p,
+					compact: true
+				}
+			}).$mount().$el);
+		},
+		menu() {
+			document.body.appendChild(new MkPostMenu({
+				propsData: {
+					source: this.$refs.menuButton,
+					post: this.p,
+					compact: true
+				}
+			}).$mount().$el);
+		}
 	}
 });
 </script>
diff --git a/src/web/app/mobile/views/components/timeline.vue b/src/web/app/mobile/views/components/timeline.vue
index 80fda7560..13f597360 100644
--- a/src/web/app/mobile/views/components/timeline.vue
+++ b/src/web/app/mobile/views/components/timeline.vue
@@ -9,7 +9,7 @@
 			%fa:R comments%
 			%i18n:mobile.tags.mk-home-timeline.empty-timeline%
 		</div>
-		<button v-if="canFetchMore" @click="more" :disabled="fetching" slot="tail">
+		<button @click="more" :disabled="fetching" slot="tail">
 			<span v-if="!fetching">%i18n:mobile.tags.mk-timeline.load-more%</span>
 			<span v-if="fetching">%i18n:common.loading%<mk-ellipsis/></span>
 		</button>
diff --git a/src/web/app/mobile/views/components/ui.header.vue b/src/web/app/mobile/views/components/ui.header.vue
index b9b7b4771..2df5ea162 100644
--- a/src/web/app/mobile/views/components/ui.header.vue
+++ b/src/web/app/mobile/views/components/ui.header.vue
@@ -24,7 +24,6 @@ export default Vue.extend({
 	props: ['func'],
 	data() {
 		return {
-			func: null,
 			hasUnreadNotifications: false,
 			hasUnreadMessagingMessages: false,
 			connection: null,
diff --git a/src/web/app/mobile/views/components/ui.nav.vue b/src/web/app/mobile/views/components/ui.nav.vue
index 3796b2765..5ca7e2e94 100644
--- a/src/web/app/mobile/views/components/ui.nav.vue
+++ b/src/web/app/mobile/views/components/ui.nav.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="nav" :style="{ display: isOpen ? 'block' : 'none' }">
-	<div class="backdrop" @click="parent.toggleDrawer"></div>
+	<div class="backdrop" @click="$parent.isDrawerOpening = false"></div>
 	<div class="body">
 		<router-link class="me" v-if="os.isSignedIn" :to="`/${os.i.username}`">
 			<img class="avatar" :src="`${os.i.avatar_url}?thumbnail&size=128`" alt="avatar"/>
@@ -8,36 +8,40 @@
 		</router-link>
 		<div class="links">
 			<ul>
-				<li><router-link href="/">%fa:home%%i18n:mobile.tags.mk-ui-nav.home%%fa:angle-right%</router-link></li>
-				<li><router-link href="/i/notifications">%fa:R bell%%i18n:mobile.tags.mk-ui-nav.notifications%<template v-if="hasUnreadNotifications">%fa:circle%</template>%fa:angle-right%</router-link></li>
-				<li><router-link href="/i/messaging">%fa:R comments%%i18n:mobile.tags.mk-ui-nav.messaging%<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>%fa:angle-right%</router-link></li>
+				<li><router-link to="/">%fa:home%%i18n:mobile.tags.mk-ui-nav.home%%fa:angle-right%</router-link></li>
+				<li><router-link to="/i/notifications">%fa:R bell%%i18n:mobile.tags.mk-ui-nav.notifications%<template v-if="hasUnreadNotifications">%fa:circle%</template>%fa:angle-right%</router-link></li>
+				<li><router-link to="/i/messaging">%fa:R comments%%i18n:mobile.tags.mk-ui-nav.messaging%<template v-if="hasUnreadMessagingMessages">%fa:circle%</template>%fa:angle-right%</router-link></li>
 			</ul>
 			<ul>
 				<li><a :href="chUrl" target="_blank">%fa:tv%%i18n:mobile.tags.mk-ui-nav.ch%%fa:angle-right%</a></li>
-				<li><router-link href="/i/drive">%fa:cloud%%i18n:mobile.tags.mk-ui-nav.drive%%fa:angle-right%</router-link></li>
+				<li><router-link to="/i/drive">%fa:cloud%%i18n:mobile.tags.mk-ui-nav.drive%%fa:angle-right%</router-link></li>
 			</ul>
 			<ul>
 				<li><a @click="search">%fa:search%%i18n:mobile.tags.mk-ui-nav.search%%fa:angle-right%</a></li>
 			</ul>
 			<ul>
-				<li><router-link href="/i/settings">%fa:cog%%i18n:mobile.tags.mk-ui-nav.settings%%fa:angle-right%</router-link></li>
+				<li><router-link to="/i/settings">%fa:cog%%i18n:mobile.tags.mk-ui-nav.settings%%fa:angle-right%</router-link></li>
 			</ul>
 		</div>
-		<a :href="aboutUrl"><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
+		<a :href="docsUrl"><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
 	</div>
 </div>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
+import { docsUrl, chUrl } from '../../../config';
 
 export default Vue.extend({
+	props: ['isOpen'],
 	data() {
 		return {
 			hasUnreadNotifications: false,
 			hasUnreadMessagingMessages: false,
 			connection: null,
-			connectionId: null
+			connectionId: null,
+			docsUrl,
+			chUrl
 		};
 	},
 	mounted() {