From abc082f7c06bdefe2eae9d7493757e770897d151 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 24 Jun 2018 13:08:48 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88=E3=83=95?=
 =?UTF-8?q?=E3=82=A9=E3=83=AD=E3=83=BC=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0?=
 =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85=E3=81=99=E3=82=8B=E3=81=AA=E3=81=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 locales/ja.yml                                |   7 +
 src/client/app/common/views/pages/follow.vue  | 215 ++++++++++++++++++
 src/client/app/desktop/script.ts              |   4 +-
 .../app/desktop/views/pages/user/user.vue     |   6 +-
 src/client/app/mobile/script.ts               |   4 +-
 src/server/activitypub.ts                     |  16 +-
 6 files changed, 232 insertions(+), 20 deletions(-)
 create mode 100644 src/client/app/common/views/pages/follow.vue

diff --git a/locales/ja.yml b/locales/ja.yml
index d3d04eb28a..0f1d9cd510 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -280,6 +280,13 @@ common/views/widgets/memo.vue:
   memo: "ここに書いて!"
   save: "保存"
 
+common/views/pages/follow.vue:
+  signed-in-as: "{}としてサインイン中"
+  following: "フォロー中"
+  follow: "フォロー"
+  request-pending: "フォロー許可待ち"
+  follow-request: "フォロー申請"
+
 desktop/views/components/activity.chart.vue:
   total: "Black ... Total"
   notes: "Blue ... Notes"
diff --git a/src/client/app/common/views/pages/follow.vue b/src/client/app/common/views/pages/follow.vue
new file mode 100644
index 0000000000..c8e838be85
--- /dev/null
+++ b/src/client/app/common/views/pages/follow.vue
@@ -0,0 +1,215 @@
+<template>
+<div class="syxhndwprovvuqhmyvveewmbqayniwkv" v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
+	<div class="signed-in-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + myName + '</b>')"></div>
+
+	<main>
+		<div class="banner" :style="bannerStyle"></div>
+		<mk-avatar class="avatar" :user="user" :disable-preview="true"/>
+		<div class="body">
+			<router-link :to="user | userPage" class="name">{{ user | userName }}</router-link>
+			<span class="username">@{{ user | acct }}</span>
+			<div class="description">
+				<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
+			</div>
+		</div>
+	</main>
+
+	<button
+			:class="{ wait: followWait, active: user.isFollowing || user.hasPendingFollowRequestFromYou }"
+			@click="onClick"
+			:disabled="followWait">
+		<template v-if="!followWait">
+			<template v-if="user.hasPendingFollowRequestFromYou">%fa:hourglass-half% %i18n:@request-pending%</template>
+			<template v-else-if="user.isFollowing">%fa:minus% %i18n:@following%</template>
+			<template v-else-if="!user.isFollowing && user.isLocked">%fa:plus% %i18n:@follow-request%</template>
+			<template v-else-if="!user.isFollowing && !user.isLocked">%fa:plus% %i18n:@follow%</template>
+		</template>
+		<template v-else>%fa:spinner .pulse .fw%</template>
+	</button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import parseAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
+import Progress from '../../../common/scripts/loading';
+
+export default Vue.extend({
+	data() {
+		return {
+			fetching: true,
+			user: null,
+			followWait: false
+		};
+	},
+
+	computed: {
+		myName(): string {
+			return Vue.filter('userName')(this.$store.state.i);
+		},
+
+		bannerStyle(): any {
+			if (this.user.bannerUrl == null) return {};
+			return {
+				backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
+				backgroundImage: `url(${ this.user.bannerUrl })`
+			};
+		}
+	},
+
+	created() {
+		this.fetch();
+	},
+
+	methods: {
+		fetch() {
+			const acct = new URL(location.href).searchParams.get('acct');
+			this.fetching = true;
+			Progress.start();
+			(this as any).api('users/show', parseAcct(acct)).then(user => {
+				this.user = user;
+				this.fetching = false;
+				Progress.done();
+				document.title = getUserName(this.user) + ' | Misskey';
+			});
+		},
+
+		async onClick() {
+			this.followWait = true;
+
+			try {
+				if (this.user.isFollowing) {
+					this.user = await (this as any).api('following/delete', {
+						userId: this.user.id
+					});
+				} else {
+					if (this.user.isLocked && this.user.hasPendingFollowRequestFromYou) {
+						this.user = await (this as any).api('following/requests/cancel', {
+							userId: this.user.id
+						});
+					} else if (this.user.isLocked) {
+						this.user = await (this as any).api('following/create', {
+							userId: this.user.id
+						});
+					} else {
+						this.user = await (this as any).api('following/create', {
+							userId: this.user.id
+						});
+					}
+				}
+			} catch (e) {
+				console.error(e);
+			} finally {
+				this.followWait = false;
+			}
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+	padding 32px
+	max-width 500px
+	margin 0 auto
+	text-align center
+	color isDark ? #9baec8 : #868c8c
+
+	$bg = isDark ? #282C37 : #fff
+
+	@media (max-width 400px)
+		padding 16px
+
+	> .signed-in-as
+		margin-bottom 16px
+		font-size 14px
+		color isDark ? #9baec8 : #9daab3
+
+	> main
+		margin-bottom 16px
+		background $bg
+		border-radius 8px
+		box-shadow 0 4px 12px rgba(#000, 0.1)
+		overflow hidden
+
+		> .banner
+			height 128px
+			background-position center
+			background-size cover
+
+		> .avatar
+			display block
+			margin -50px auto 0 auto
+			width 100px
+			height 100px
+			border-radius 100%
+			border solid 4px $bg
+
+		> .body
+			padding 4px 32px 32px 32px
+
+			@media (max-width 400px)
+				padding 4px 16px 16px 16px
+
+			> .name
+				font-size 20px
+				font-weight bold
+
+			> .username
+				display block
+				opacity 0.7
+
+			> .description
+				margin-top 16px
+
+	> button
+		display block
+		user-select none
+		cursor pointer
+		padding 10px 16px
+		margin 0
+		width 100%
+		min-width 150px
+		font-size 14px
+		font-weight bold
+		color $theme-color
+		background transparent
+		outline none
+		border solid 1px $theme-color
+		border-radius 36px
+
+		&:hover
+			background rgba($theme-color, 0.1)
+
+		&:active
+			background rgba($theme-color, 0.2)
+
+		&.active
+			color $theme-color-foreground
+			background $theme-color
+
+			&:hover
+				background lighten($theme-color, 10%)
+				border-color lighten($theme-color, 10%)
+
+			&:active
+				background darken($theme-color, 10%)
+				border-color darken($theme-color, 10%)
+
+		&.wait
+			cursor wait !important
+			opacity 0.7
+
+		*
+			pointer-events none
+
+.syxhndwprovvuqhmyvveewmbqayniwkv[data-darkmode]
+	root(true)
+
+.syxhndwprovvuqhmyvveewmbqayniwkv:not([data-darkmode])
+	root(false)
+
+</style>
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index beb358c7ee..297100e0e0 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -36,6 +36,7 @@ import MkSearch from './views/pages/search.vue';
 import MkTag from './views/pages/tag.vue';
 import MkReversi from './views/pages/reversi.vue';
 import MkShare from './views/pages/share.vue';
+import MkFollow from '../common/views/pages/follow.vue';
 
 /**
  * init
@@ -67,7 +68,8 @@ init(async (launch) => {
 			{ path: '/reversi', component: MkReversi },
 			{ path: '/reversi/:game', component: MkReversi },
 			{ path: '/@:user', component: MkUser },
-			{ path: '/notes/:note', component: MkNote }
+			{ path: '/notes/:note', component: MkNote },
+			{ path: '/authorize-follow', component: MkFollow }
 		]
 	});
 
diff --git a/src/client/app/desktop/views/pages/user/user.vue b/src/client/app/desktop/views/pages/user/user.vue
index 0337befdbd..fc5c900037 100644
--- a/src/client/app/desktop/views/pages/user/user.vue
+++ b/src/client/app/desktop/views/pages/user/user.vue
@@ -1,6 +1,6 @@
 <template>
 <mk-ui>
-	<div class="zwwan0di1v4356rmdbjmwnn32tptpdp2" v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
+	<div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
 		<div class="is-suspended" v-if="user.isSuspended">%fa:exclamation-triangle% %i18n:@is-suspended%</div>
 		<div class="is-remote" v-if="user.host != null">%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></div>
 		<main>
@@ -149,10 +149,10 @@ root(isDark)
 				i
 					color #ccc
 
-.zwwan0di1v4356rmdbjmwnn32tptpdp2[data-darkmode]
+.xygkxeaeontfaokvqmiblezmhvhostak[data-darkmode]
 	root(true)
 
-.zwwan0di1v4356rmdbjmwnn32tptpdp2:not([data-darkmode])
+.xygkxeaeontfaokvqmiblezmhvhostak:not([data-darkmode])
 	root(false)
 
 </style>
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index cfa9654e61..cb43f9d520 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -38,6 +38,7 @@ import MkSettings from './views/pages/settings.vue';
 import MkReversi from './views/pages/reversi.vue';
 import MkTag from './views/pages/tag.vue';
 import MkShare from './views/pages/share.vue';
+import MkFollow from '../common/views/pages/follow.vue';
 
 /**
  * init
@@ -80,7 +81,8 @@ init((launch) => {
 			{ path: '/@:user', component: MkUser },
 			{ path: '/@:user/followers', component: MkFollowers },
 			{ path: '/@:user/following', component: MkFollowing },
-			{ path: '/notes/:note', component: MkNote }
+			{ path: '/notes/:note', component: MkNote },
+			{ path: '/authorize-follow', component: MkFollow }
 		]
 	});
 
diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts
index 1fbc621e91..f8a01a6ffe 100644
--- a/src/server/activitypub.ts
+++ b/src/server/activitypub.ts
@@ -11,7 +11,7 @@ import renderNote from '../remote/activitypub/renderer/note';
 import renderKey from '../remote/activitypub/renderer/key';
 import renderPerson from '../remote/activitypub/renderer/person';
 import renderOrderedCollection from '../remote/activitypub/renderer/ordered-collection';
-//import parseAcct from '../acct/parse';
+import parseAcct from '../acct/parse';
 import config from '../config';
 
 // Init router
@@ -142,20 +142,6 @@ router.get('/@:user', async (ctx, next) => {
 
 	userInfo(ctx, user);
 });
-
-// follow form
-router.get('/authorize-follow', async ctx => {
-	/* TODO
-	const { username, host } = parseAcct(ctx.query.acct);
-	if (host === null) {
-		res.sendStatus(422);
-		return;
-	}
-
-	const finger = await request(`https://${host}`)
-	*/
-});
-
 //#endregion
 
 export default router;