From c869883d76455844e8d56ec4e863c6405489f897 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 10 Feb 2018 17:01:32 +0900
Subject: [PATCH] wip

---
 .../app/common/views/components/signin.vue    |   2 +-
 .../app/common/views/components/signup.vue    | 452 ++++++++----------
 src/web/app/config.ts                         |  11 +
 src/web/app/desktop/views/pages/welcome.vue   |   2 +-
 src/web/app/init.ts                           |   3 +
 webpack/module/rules/fa.ts                    |   2 +-
 webpack/module/rules/i18n.ts                  |   2 +-
 7 files changed, 217 insertions(+), 257 deletions(-)
 create mode 100644 src/web/app/config.ts

diff --git a/src/web/app/common/views/components/signin.vue b/src/web/app/common/views/components/signin.vue
index 5ffc518b3c..ee26110a43 100644
--- a/src/web/app/common/views/components/signin.vue
+++ b/src/web/app/common/views/components/signin.vue
@@ -13,7 +13,7 @@
 </form>
 </template>
 
-<script lang="ts">
+<script>
 import Vue from 'vue';
 
 export default Vue.extend({
diff --git a/src/web/app/common/views/components/signup.vue b/src/web/app/common/views/components/signup.vue
index 1734f77316..723555cdc4 100644
--- a/src/web/app/common/views/components/signup.vue
+++ b/src/web/app/common/views/components/signup.vue
@@ -1,9 +1,9 @@
 <template>
-<form @submit.prevent="onSubmit" autocomplete="off">
+<form class="form" @submit.prevent="onSubmit" autocomplete="off">
 	<label class="username">
 		<p class="caption">%fa:at%%i18n:common.tags.mk-signup.username%</p>
 		<input v-model="username" type="text" pattern="^[a-zA-Z0-9-]{3,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required @keyup="onChangeUsername"/>
-		<p class="profile-page-url-preview" v-if="refs.username.value != '' && username-state != 'invalidFormat' && username-state != 'minRange' && username-state != 'maxRange'">{ _URL_ + '/' + refs.username.value }</p>
+		<p class="profile-page-url-preview" v-if="username != '' && username-state != 'invalidFormat' && username-state != 'minRange' && username-state != 'maxRange'">{ _URL_ + '/' + refs.username.value }</p>
 		<p class="info" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%%i18n:common.tags.mk-signup.checking%</p>
 		<p class="info" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.available%</p>
 		<p class="info" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.unavailable%</p>
@@ -30,7 +30,7 @@
 	</label>
 	<label class="recaptcha">
 		<p class="caption"><template v-if="recaptchaed">%fa:toggle-on%</template><template v-if="!recaptchaed">%fa:toggle-off%</template>%i18n:common.tags.mk-signup.recaptcha%</p>
-		<div v-if="recaptcha" class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" data-sitekey="recaptcha.site_key"></div>
+		<div v-if="recaptcha" class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" :data-sitekey="recaptchaSitekey"></div>
 	</label>
 	<label class="agree-tou">
 		<input name="agree-tou" type="checkbox" autocomplete="off" required/>
@@ -43,16 +43,98 @@
 <script lang="ts">
 import Vue from 'vue';
 const getPasswordStrength = require('syuilo-password-strength');
-import
-
-const aboutUrl = `${_DOCS_URL_}/${_LANG_}/tou`;
+import { docsUrl, lang, recaptchaSitekey } from '../../../config';
 
 export default Vue.extend({
-	methods: {
-		onSubmit() {
-
+	props: ['os'],
+	data() {
+		return {
+			username: '',
+			password: '',
+			retypedPassword: '',
+			touUrl: `${docsUrl}/${lang}/tou`,
+			recaptchaSitekey,
+			recaptchaed: false,
+			usernameState: null,
+			passwordStrength: '',
+			passwordRetypeState: null
 		}
 	},
+	methods: {
+		onChangeUsername() {
+			if (this.username == '') {
+				this.usernameState = null;
+				return;
+			}
+
+			const err =
+				!this.username.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' :
+				this.username.length < 3 ? 'min-range' :
+				this.username.length > 20 ? 'max-range' :
+				null;
+
+			if (err) {
+				this.usernameState = err;
+				return;
+			}
+
+			this.usernameState = 'wait';
+
+			this.os.api('username/available', {
+				username: this.username
+			}).then(result => {
+				this.usernameState = result.available ? 'ok' : 'unavailable';
+			}).catch(err => {
+				this.usernameState = 'error';
+			});
+		},
+		onChangePassword() {
+			if (this.password == '') {
+				this.passwordStrength = '';
+				return;
+			}
+
+			const strength = getPasswordStrength(this.password);
+			this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
+			(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
+		},
+		onChangePasswordRetype() {
+			if (this.retypedPassword == '') {
+				this.passwordRetypeState = null;
+				return;
+			}
+
+			this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match';
+		},
+		onSubmit() {
+			this.os.api('signup', {
+				username: this.username,
+				password: this.password,
+				'g-recaptcha-response': (window as any).grecaptcha.getResponse()
+			}).then(() => {
+				this.os.api('signin', {
+					username: this.username,
+					password: this.password
+				}).then(() => {
+					location.href = '/';
+				});
+			}).catch(() => {
+				alert('%i18n:common.tags.mk-signup.some-error%');
+
+				(window as any).grecaptcha.reset();
+				this.recaptchaed = false;
+			});
+		}
+	},
+	created() {
+		(window as any).onRecaptchaed = () => {
+			this.recaptchaed = true;
+		};
+
+		(window as any).onRecaptchaExpired = () => {
+			this.recaptchaed = false;
+		};
+	},
 	mounted() {
 		const head = document.getElementsByTagName('head')[0];
 		const script = document.createElement('script');
@@ -63,269 +145,133 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-	:scope
+.form
+	min-width 302px
+
+	label
 		display block
-		min-width 302px
-		overflow hidden
+		margin 16px 0
 
-		> form
+		> .caption
+			margin 0 0 4px 0
+			color #828888
+			font-size 0.95em
 
-			label
+			> [data-fa]
+				margin-right 0.25em
+				color #96adac
+
+		> .info
+			display block
+			margin 4px 0
+			font-size 0.8em
+
+			> [data-fa]
+				margin-right 0.3em
+
+		&.username
+			.profile-page-url-preview
 				display block
-				margin 16px 0
+				margin 4px 8px 0 4px
+				font-size 0.8em
+				color #888
 
-				> .caption
-					margin 0 0 4px 0
-					color #828888
-					font-size 0.95em
+				&:empty
+					display none
 
-					> [data-fa]
-						margin-right 0.25em
-						color #96adac
+				&:not(:empty) + .info
+					margin-top 0
 
-				> .info
+		&.password
+			.meter
+				display block
+				margin-top 8px
+				width 100%
+				height 8px
+
+				&[data-strength='']
+					display none
+
+				&[data-strength='low']
+					> .value
+						background #d73612
+
+				&[data-strength='medium']
+					> .value
+						background #d7ca12
+
+				&[data-strength='high']
+					> .value
+						background #61bb22
+
+				> .value
 					display block
-					margin 4px 0
-					font-size 0.8em
+					width 0%
+					height 100%
+					background transparent
+					border-radius 4px
+					transition all 0.1s ease
 
-					> [data-fa]
-						margin-right 0.3em
+	[type=text], [type=password]
+		user-select text
+		display inline-block
+		cursor auto
+		padding 0 12px
+		margin 0
+		width 100%
+		line-height 44px
+		font-size 1em
+		color #333 !important
+		background #fff !important
+		outline none
+		border solid 1px rgba(0, 0, 0, 0.1)
+		border-radius 4px
+		box-shadow 0 0 0 114514px #fff inset
+		transition all .3s ease
 
-				&.username
-					.profile-page-url-preview
-						display block
-						margin 4px 8px 0 4px
-						font-size 0.8em
-						color #888
+		&:hover
+			border-color rgba(0, 0, 0, 0.2)
+			transition all .1s ease
 
-						&:empty
-							display none
+		&:focus
+			color $theme-color !important
+			border-color $theme-color
+			box-shadow 0 0 0 1024px #fff inset, 0 0 0 4px rgba($theme-color, 10%)
+			transition all 0s ease
 
-						&:not(:empty) + .info
-							margin-top 0
+		&:disabled
+			opacity 0.5
 
-				&.password
-					.meter
-						display block
-						margin-top 8px
-						width 100%
-						height 8px
+	.agree-tou
+		padding 4px
+		border-radius 4px
 
-						&[data-strength='']
-							display none
+		&:hover
+			background #f4f4f4
 
-						&[data-strength='low']
-							> .value
-								background #d73612
+		&:active
+			background #eee
 
-						&[data-strength='medium']
-							> .value
-								background #d7ca12
+		&, *
+			cursor pointer
 
-						&[data-strength='high']
-							> .value
-								background #61bb22
+		p
+			display inline
+			color #555
 
-						> .value
-							display block
-							width 0%
-							height 100%
-							background transparent
-							border-radius 4px
-							transition all 0.1s ease
+	button
+		margin 0 0 32px 0
+		padding 16px
+		width 100%
+		font-size 1em
+		color #fff
+		background $theme-color
+		border-radius 3px
 
-			[type=text], [type=password]
-				user-select text
-				display inline-block
-				cursor auto
-				padding 0 12px
-				margin 0
-				width 100%
-				line-height 44px
-				font-size 1em
-				color #333 !important
-				background #fff !important
-				outline none
-				border solid 1px rgba(0, 0, 0, 0.1)
-				border-radius 4px
-				box-shadow 0 0 0 114514px #fff inset
-				transition all .3s ease
+		&:hover
+			background lighten($theme-color, 5%)
 
-				&:hover
-					border-color rgba(0, 0, 0, 0.2)
-					transition all .1s ease
-
-				&:focus
-					color $theme-color !important
-					border-color $theme-color
-					box-shadow 0 0 0 1024px #fff inset, 0 0 0 4px rgba($theme-color, 10%)
-					transition all 0s ease
-
-				&:disabled
-					opacity 0.5
-
-			.agree-tou
-				padding 4px
-				border-radius 4px
-
-				&:hover
-					background #f4f4f4
-
-				&:active
-					background #eee
-
-				&, *
-					cursor pointer
-
-				p
-					display inline
-					color #555
-
-			button
-				margin 0 0 32px 0
-				padding 16px
-				width 100%
-				font-size 1em
-				color #fff
-				background $theme-color
-				border-radius 3px
-
-				&:hover
-					background lighten($theme-color, 5%)
-
-				&:active
-					background darken($theme-color, 5%)
+		&:active
+			background darken($theme-color, 5%)
 
 </style>
-
-<script lang="typescript">
-	this.mixin('api');
-
-
-	this.usernameState = null;
-	this.passwordStrength = '';
-	this.passwordRetypeState = null;
-	this.recaptchaed = false;
-
-	this.aboutUrl = `${_DOCS_URL_}/${_LANG_}/tou`;
-
-	window.onRecaptchaed = () => {
-		this.recaptchaed = true;
-		this.update();
-	};
-
-	window.onRecaptchaExpired = () => {
-		this.recaptchaed = false;
-		this.update();
-	};
-
-	this.on('mount', () => {
-		this.update({
-			recaptcha: {
-				site_key: _RECAPTCHA_SITEKEY_
-			}
-		});
-
-		const head = document.getElementsByTagName('head')[0];
-		const script = document.createElement('script');
-		script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
-		head.appendChild(script);
-	});
-
-	this.onChangeUsername = () => {
-		const username = this.$refs.username.value;
-
-		if (username == '') {
-			this.update({
-				usernameState: null
-			});
-			return;
-		}
-
-		const err =
-			!username.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' :
-			username.length < 3 ? 'min-range' :
-			username.length > 20 ? 'max-range' :
-			null;
-
-		if (err) {
-			this.update({
-				usernameState: err
-			});
-			return;
-		}
-
-		this.update({
-			usernameState: 'wait'
-		});
-
-		this.api('username/available', {
-			username: username
-		}).then(result => {
-			this.update({
-				usernameState: result.available ? 'ok' : 'unavailable'
-			});
-		}).catch(err => {
-			this.update({
-				usernameState: 'error'
-			});
-		});
-	};
-
-	this.onChangePassword = () => {
-		const password = this.$refs.password.value;
-
-		if (password == '') {
-			this.passwordStrength = '';
-			return;
-		}
-
-		const strength = getPasswordStrength(password);
-		this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
-		this.update();
-		this.$refs.passwordMetar.style.width = `${strength * 100}%`;
-	};
-
-	this.onChangePasswordRetype = () => {
-		const password = this.$refs.password.value;
-		const retypedPassword = this.$refs.passwordRetype.value;
-
-		if (retypedPassword == '') {
-			this.passwordRetypeState = null;
-			return;
-		}
-
-		this.passwordRetypeState = password == retypedPassword ? 'match' : 'not-match';
-	};
-
-	this.onsubmit = e => {
-		e.preventDefault();
-
-		const username = this.$refs.username.value;
-		const password = this.$refs.password.value;
-
-		const locker = document.body.appendChild(document.createElement('mk-locker'));
-
-		this.api('signup', {
-			username: username,
-			password: password,
-			'g-recaptcha-response': grecaptcha.getResponse()
-		}).then(() => {
-			this.api('signin', {
-				username: username,
-				password: password
-			}).then(() => {
-				location.href = '/';
-			});
-		}).catch(() => {
-			alert('%i18n:common.tags.mk-signup.some-error%');
-
-			grecaptcha.reset();
-			this.recaptchaed = false;
-
-			locker.parentNode.removeChild(locker);
-		});
-
-		return false;
-	};
-</script>
diff --git a/src/web/app/config.ts b/src/web/app/config.ts
new file mode 100644
index 0000000000..8357cf6c72
--- /dev/null
+++ b/src/web/app/config.ts
@@ -0,0 +1,11 @@
+declare const _HOST_: string;
+declare const _URL_: string;
+declare const _DOCS_URL_: string;
+declare const _LANG_: string;
+declare const _RECAPTCHA_SITEKEY_: string;
+
+export const host = _HOST_;
+export const url = _URL_;
+export const docsUrl = _DOCS_URL_;
+export const lang = _LANG_;
+export const recaptchaSitekey = _RECAPTCHA_SITEKEY_;
diff --git a/src/web/app/desktop/views/pages/welcome.vue b/src/web/app/desktop/views/pages/welcome.vue
index 68b5f4cc98..b47e82faed 100644
--- a/src/web/app/desktop/views/pages/welcome.vue
+++ b/src/web/app/desktop/views/pages/welcome.vue
@@ -18,7 +18,7 @@
 		</div>
 	</footer>
 	<modal name="signup">
-		<mk-signup/>
+		<mk-signup></mk-signup>
 	</modal>
 </div>
 </template>
diff --git a/src/web/app/init.ts b/src/web/app/init.ts
index 20ea1df8b2..3ae2a8adcd 100644
--- a/src/web/app/init.ts
+++ b/src/web/app/init.ts
@@ -70,6 +70,9 @@ export default (callback: (os: MiOS, launch: () => Vue) => void, sw = false) =>
 		// アプリ基底要素マウント
 		document.body.innerHTML = '<div id="app"></div>';
 
+		// Register global components
+		require('./common/views/components');
+
 		const launch = () => {
 			return new Vue({
 				router: new VueRouter({
diff --git a/webpack/module/rules/fa.ts b/webpack/module/rules/fa.ts
index 891b78ece2..2679089239 100644
--- a/webpack/module/rules/fa.ts
+++ b/webpack/module/rules/fa.ts
@@ -7,7 +7,7 @@ import { pattern, replacement } from '../../../src/common/build/fa';
 
 export default () => ({
 	enforce: 'pre',
-	test: /\.(tag|js|ts)$/,
+	test: /\.(vue|js|ts)$/,
 	exclude: /node_modules/,
 	loader: StringReplacePlugin.replace({
 		replacements: [{
diff --git a/webpack/module/rules/i18n.ts b/webpack/module/rules/i18n.ts
index 7261548be5..f8063a311f 100644
--- a/webpack/module/rules/i18n.ts
+++ b/webpack/module/rules/i18n.ts
@@ -10,7 +10,7 @@ export default lang => {
 
 	return {
 		enforce: 'pre',
-		test: /\.(tag|js|ts)$/,
+		test: /\.(vue|js|ts)$/,
 		exclude: /node_modules/,
 		loader: StringReplacePlugin.replace({
 			replacements: [{