wip
This commit is contained in:
parent
dd60907abe
commit
4f1795b97b
16 changed files with 576 additions and 551 deletions
|
@ -173,6 +173,7 @@
|
|||
"uuid": "3.2.1",
|
||||
"vhost": "3.0.2",
|
||||
"vue": "^2.5.13",
|
||||
"vue-js-modal": "^1.3.9",
|
||||
"vue-loader": "^14.1.1",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.13",
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
<mk-signin>
|
||||
<form :class="{ signing: signing }" onsubmit={ onsubmit }>
|
||||
<label class="user-name">
|
||||
<input ref="username" type="text" pattern="^[a-zA-Z0-9-]+$" placeholder="%i18n:common.tags.mk-signin.username%" autofocus="autofocus" required="required" oninput={ oninput }/>%fa:at%
|
||||
</label>
|
||||
<label class="password">
|
||||
<input ref="password" type="password" placeholder="%i18n:common.tags.mk-signin.password%" required="required"/>%fa:lock%
|
||||
</label>
|
||||
<label class="token" v-if="user && user.two_factor_enabled">
|
||||
<input ref="token" type="number" placeholder="%i18n:common.tags.mk-signin.token%" required="required"/>%fa:lock%
|
||||
</label>
|
||||
<button type="submit" disabled={ signing }>{ signing ? '%i18n:common.tags.mk-signin.signing-in%' : '%i18n:common.tags.mk-signin.signin%' }</button>
|
||||
</form>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
|
||||
> form
|
||||
display block
|
||||
z-index 2
|
||||
|
||||
&.signing
|
||||
&, *
|
||||
cursor wait !important
|
||||
|
||||
label
|
||||
display block
|
||||
margin 12px 0
|
||||
|
||||
[data-fa]
|
||||
display block
|
||||
pointer-events none
|
||||
position absolute
|
||||
bottom 0
|
||||
top 0
|
||||
left 0
|
||||
z-index 1
|
||||
margin auto
|
||||
padding 0 16px
|
||||
height 1em
|
||||
color #898786
|
||||
|
||||
input[type=text]
|
||||
input[type=password]
|
||||
input[type=number]
|
||||
user-select text
|
||||
display inline-block
|
||||
cursor auto
|
||||
padding 0 0 0 38px
|
||||
margin 0
|
||||
width 100%
|
||||
line-height 44px
|
||||
font-size 1em
|
||||
color rgba(0, 0, 0, 0.7)
|
||||
background #fff
|
||||
outline none
|
||||
border solid 1px #eee
|
||||
border-radius 4px
|
||||
|
||||
&:hover
|
||||
background rgba(255, 255, 255, 0.7)
|
||||
border-color #ddd
|
||||
|
||||
& + i
|
||||
color #797776
|
||||
|
||||
&:focus
|
||||
background #fff
|
||||
border-color #ccc
|
||||
|
||||
& + i
|
||||
color #797776
|
||||
|
||||
[type=submit]
|
||||
cursor pointer
|
||||
padding 16px
|
||||
margin -6px 0 0 0
|
||||
width 100%
|
||||
font-size 1.2em
|
||||
color rgba(0, 0, 0, 0.5)
|
||||
outline none
|
||||
border none
|
||||
border-radius 0
|
||||
background transparent
|
||||
transition all .5s ease
|
||||
|
||||
&:hover
|
||||
color $theme-color
|
||||
transition all .2s ease
|
||||
|
||||
&:focus
|
||||
color $theme-color
|
||||
transition all .2s ease
|
||||
|
||||
&:active
|
||||
color darken($theme-color, 30%)
|
||||
transition all .2s ease
|
||||
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.mixin('api');
|
||||
|
||||
this.user = null;
|
||||
this.signing = false;
|
||||
|
||||
this.oninput = () => {
|
||||
this.api('users/show', {
|
||||
username: this.$refs.username.value
|
||||
}).then(user => {
|
||||
this.user = user;
|
||||
this.$emit('user', user);
|
||||
this.update();
|
||||
});
|
||||
};
|
||||
|
||||
this.onsubmit = e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.$refs.username.value == '') {
|
||||
this.$refs.username.focus();
|
||||
return false;
|
||||
}
|
||||
if (this.$refs.password.value == '') {
|
||||
this.$refs.password.focus();
|
||||
return false;
|
||||
}
|
||||
if (this.user && this.user.two_factor_enabled && this.$refs.token.value == '') {
|
||||
this.$refs.token.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.update({
|
||||
signing: true
|
||||
});
|
||||
|
||||
this.api('signin', {
|
||||
username: this.$refs.username.value,
|
||||
password: this.$refs.password.value,
|
||||
token: this.user && this.user.two_factor_enabled ? this.$refs.token.value : undefined
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
}).catch(() => {
|
||||
alert('something happened');
|
||||
this.update({
|
||||
signing: false
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
</mk-signin>
|
|
@ -1,307 +0,0 @@
|
|||
<mk-signup>
|
||||
<form onsubmit={ onsubmit } autocomplete="off">
|
||||
<label class="username">
|
||||
<p class="caption">%fa:at%%i18n:common.tags.mk-signup.username%</p>
|
||||
<input ref="username" type="text" pattern="^[a-zA-Z0-9-]{3,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required="required" onkeyup={ 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="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>
|
||||
<p class="info" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.error%</p>
|
||||
<p class="info" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.invalid-format%</p>
|
||||
<p class="info" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.too-short%</p>
|
||||
<p class="info" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.too-long%</p>
|
||||
</label>
|
||||
<label class="password">
|
||||
<p class="caption">%fa:lock%%i18n:common.tags.mk-signup.password%</p>
|
||||
<input ref="password" type="password" placeholder="%i18n:common.tags.mk-signup.password-placeholder%" autocomplete="off" required="required" onkeyup={ onChangePassword }/>
|
||||
<div class="meter" v-if="passwordStrength != ''" data-strength={ passwordStrength }>
|
||||
<div class="value" ref="passwordMetar"></div>
|
||||
</div>
|
||||
<p class="info" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.weak-password%</p>
|
||||
<p class="info" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.normal-password%</p>
|
||||
<p class="info" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.strong-password%</p>
|
||||
</label>
|
||||
<label class="retype-password">
|
||||
<p class="caption">%fa:lock%%i18n:common.tags.mk-signup.password%(%i18n:common.tags.mk-signup.retype%)</p>
|
||||
<input ref="passwordRetype" type="password" placeholder="%i18n:common.tags.mk-signup.retype-placeholder%" autocomplete="off" required="required" onkeyup={ onChangePasswordRetype }/>
|
||||
<p class="info" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.password-matched%</p>
|
||||
<p class="info" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.password-not-matched%</p>
|
||||
</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>
|
||||
</label>
|
||||
<label class="agree-tou">
|
||||
<input name="agree-tou" type="checkbox" autocomplete="off" required="required"/>
|
||||
<p><a href={ touUrl } target="_blank">利用規約</a>に同意する</p>
|
||||
</label>
|
||||
<button @click="onsubmit">%i18n:common.tags.mk-signup.create%</button>
|
||||
</form>
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
min-width 302px
|
||||
overflow hidden
|
||||
|
||||
> form
|
||||
|
||||
label
|
||||
display block
|
||||
margin 16px 0
|
||||
|
||||
> .caption
|
||||
margin 0 0 4px 0
|
||||
color #828888
|
||||
font-size 0.95em
|
||||
|
||||
> [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 4px 8px 0 4px
|
||||
font-size 0.8em
|
||||
color #888
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
&:not(:empty) + .info
|
||||
margin-top 0
|
||||
|
||||
&.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
|
||||
width 0%
|
||||
height 100%
|
||||
background transparent
|
||||
border-radius 4px
|
||||
transition all 0.1s ease
|
||||
|
||||
[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
|
||||
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%)
|
||||
|
||||
</style>
|
||||
<script lang="typescript">
|
||||
this.mixin('api');
|
||||
const getPasswordStrength = require('syuilo-password-strength');
|
||||
|
||||
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>
|
||||
</mk-signup>
|
7
src/web/app/common/views/components/index.ts
Normal file
7
src/web/app/common/views/components/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import signin from './signin.vue';
|
||||
import signup from './signup.vue';
|
||||
|
||||
Vue.component('mk-signin', signin);
|
||||
Vue.component('mk-signup', signup);
|
138
src/web/app/common/views/components/signin.vue
Normal file
138
src/web/app/common/views/components/signin.vue
Normal file
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<form class="form" :class="{ signing: signing }" @submit.prevent="onSubmit">
|
||||
<label class="user-name">
|
||||
<input v-model="username" type="text" pattern="^[a-zA-Z0-9-]+$" placeholder="%i18n:common.tags.mk-signin.username%" autofocus required @change="onUsernameChange"/>%fa:at%
|
||||
</label>
|
||||
<label class="password">
|
||||
<input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signin.password%" required/>%fa:lock%
|
||||
</label>
|
||||
<label class="token" v-if="user && user.two_factor_enabled">
|
||||
<input v-model="token" type="number" placeholder="%i18n:common.tags.mk-signin.token%" required/>%fa:lock%
|
||||
</label>
|
||||
<button type="submit" disabled={ signing }>{ signing ? '%i18n:common.tags.mk-signin.signing-in%' : '%i18n:common.tags.mk-signin.signin%' }</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['os'],
|
||||
data() {
|
||||
return {
|
||||
signing: false,
|
||||
user: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onUsernameChange() {
|
||||
this.os.api('users/show', {
|
||||
username: this.username
|
||||
}).then(user => {
|
||||
this.user = user;
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
this.signing = true;
|
||||
|
||||
this.os.api('signin', {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
token: this.user && this.user.two_factor_enabled ? this.token : undefined
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
}).catch(() => {
|
||||
alert('something happened');
|
||||
this.signing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.form
|
||||
display block
|
||||
z-index 2
|
||||
|
||||
&.signing
|
||||
&, *
|
||||
cursor wait !important
|
||||
|
||||
label
|
||||
display block
|
||||
margin 12px 0
|
||||
|
||||
[data-fa]
|
||||
display block
|
||||
pointer-events none
|
||||
position absolute
|
||||
bottom 0
|
||||
top 0
|
||||
left 0
|
||||
z-index 1
|
||||
margin auto
|
||||
padding 0 16px
|
||||
height 1em
|
||||
color #898786
|
||||
|
||||
input[type=text]
|
||||
input[type=password]
|
||||
input[type=number]
|
||||
user-select text
|
||||
display inline-block
|
||||
cursor auto
|
||||
padding 0 0 0 38px
|
||||
margin 0
|
||||
width 100%
|
||||
line-height 44px
|
||||
font-size 1em
|
||||
color rgba(0, 0, 0, 0.7)
|
||||
background #fff
|
||||
outline none
|
||||
border solid 1px #eee
|
||||
border-radius 4px
|
||||
|
||||
&:hover
|
||||
background rgba(255, 255, 255, 0.7)
|
||||
border-color #ddd
|
||||
|
||||
& + i
|
||||
color #797776
|
||||
|
||||
&:focus
|
||||
background #fff
|
||||
border-color #ccc
|
||||
|
||||
& + i
|
||||
color #797776
|
||||
|
||||
[type=submit]
|
||||
cursor pointer
|
||||
padding 16px
|
||||
margin -6px 0 0 0
|
||||
width 100%
|
||||
font-size 1.2em
|
||||
color rgba(0, 0, 0, 0.5)
|
||||
outline none
|
||||
border none
|
||||
border-radius 0
|
||||
background transparent
|
||||
transition all .5s ease
|
||||
|
||||
&:hover
|
||||
color $theme-color
|
||||
transition all .2s ease
|
||||
|
||||
&:focus
|
||||
color $theme-color
|
||||
transition all .2s ease
|
||||
|
||||
&:active
|
||||
color darken($theme-color, 30%)
|
||||
transition all .2s ease
|
||||
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
|
||||
</style>
|
331
src/web/app/common/views/components/signup.vue
Normal file
331
src/web/app/common/views/components/signup.vue
Normal file
|
@ -0,0 +1,331 @@
|
|||
<template>
|
||||
<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="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>
|
||||
<p class="info" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.error%</p>
|
||||
<p class="info" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.invalid-format%</p>
|
||||
<p class="info" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.too-short%</p>
|
||||
<p class="info" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.too-long%</p>
|
||||
</label>
|
||||
<label class="password">
|
||||
<p class="caption">%fa:lock%%i18n:common.tags.mk-signup.password%</p>
|
||||
<input v-model="password" type="password" placeholder="%i18n:common.tags.mk-signup.password-placeholder%" autocomplete="off" required @keyup="onChangePassword"/>
|
||||
<div class="meter" v-if="passwordStrength != ''" :data-strength="passwordStrength">
|
||||
<div class="value" ref="passwordMetar"></div>
|
||||
</div>
|
||||
<p class="info" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.weak-password%</p>
|
||||
<p class="info" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.normal-password%</p>
|
||||
<p class="info" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.strong-password%</p>
|
||||
</label>
|
||||
<label class="retype-password">
|
||||
<p class="caption">%fa:lock%%i18n:common.tags.mk-signup.password%(%i18n:common.tags.mk-signup.retype%)</p>
|
||||
<input v-model="passwordRetype" type="password" placeholder="%i18n:common.tags.mk-signup.retype-placeholder%" autocomplete="off" required @keyup="onChangePasswordRetype"/>
|
||||
<p class="info" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw%%i18n:common.tags.mk-signup.password-matched%</p>
|
||||
<p class="info" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:common.tags.mk-signup.password-not-matched%</p>
|
||||
</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>
|
||||
</label>
|
||||
<label class="agree-tou">
|
||||
<input name="agree-tou" type="checkbox" autocomplete="off" required/>
|
||||
<p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
|
||||
</label>
|
||||
<button type="submit">%i18n:common.tags.mk-signup.create%</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
const getPasswordStrength = require('syuilo-password-strength');
|
||||
import
|
||||
|
||||
const aboutUrl = `${_DOCS_URL_}/${_LANG_}/tou`;
|
||||
|
||||
export default Vue.extend({
|
||||
methods: {
|
||||
onSubmit() {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
const script = document.createElement('script');
|
||||
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
|
||||
head.appendChild(script);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
:scope
|
||||
display block
|
||||
min-width 302px
|
||||
overflow hidden
|
||||
|
||||
> form
|
||||
|
||||
label
|
||||
display block
|
||||
margin 16px 0
|
||||
|
||||
> .caption
|
||||
margin 0 0 4px 0
|
||||
color #828888
|
||||
font-size 0.95em
|
||||
|
||||
> [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 4px 8px 0 4px
|
||||
font-size 0.8em
|
||||
color #888
|
||||
|
||||
&:empty
|
||||
display none
|
||||
|
||||
&:not(:empty) + .info
|
||||
margin-top 0
|
||||
|
||||
&.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
|
||||
width 0%
|
||||
height 100%
|
||||
background transparent
|
||||
border-radius 4px
|
||||
transition all 0.1s ease
|
||||
|
||||
[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
|
||||
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%)
|
||||
|
||||
</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>
|
|
@ -17,105 +17,113 @@
|
|||
<p class="c">{ _COPYRIGHT_ }</p>
|
||||
</div>
|
||||
</footer>
|
||||
<modal name="signup">
|
||||
<mk-signup/>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#wait {
|
||||
right: auto;
|
||||
left: 15px;
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
methods: {
|
||||
signup() {
|
||||
this.$modal.show('signup');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#wait {
|
||||
right: auto;
|
||||
left: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.root
|
||||
display flex
|
||||
flex-direction column
|
||||
flex 1
|
||||
background #eee
|
||||
$width = 1000px
|
||||
.root
|
||||
display flex
|
||||
flex-direction column
|
||||
flex 1
|
||||
background #eee
|
||||
$width = 1000px
|
||||
|
||||
> main
|
||||
display flex
|
||||
flex 1
|
||||
> main
|
||||
display flex
|
||||
flex 1
|
||||
max-width $width
|
||||
margin 0 auto
|
||||
padding 80px 0 0 0
|
||||
|
||||
> div:first-child
|
||||
margin 0 auto 0 0
|
||||
width calc(100% - 500px)
|
||||
color #777
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
font-weight normal
|
||||
font-variant small-caps
|
||||
letter-spacing 12px
|
||||
|
||||
> p
|
||||
margin 0.5em 0
|
||||
line-height 2em
|
||||
|
||||
button
|
||||
padding 8px 16px
|
||||
font-size inherit
|
||||
|
||||
.signup
|
||||
color $theme-color
|
||||
border solid 2px $theme-color
|
||||
border-radius 4px
|
||||
|
||||
&:focus
|
||||
box-shadow 0 0 0 3px rgba($theme-color, 0.2)
|
||||
|
||||
&:hover
|
||||
color $theme-color-foreground
|
||||
background $theme-color
|
||||
|
||||
&:active
|
||||
color $theme-color-foreground
|
||||
background darken($theme-color, 10%)
|
||||
border-color darken($theme-color, 10%)
|
||||
|
||||
.signin
|
||||
&:focus
|
||||
color #444
|
||||
|
||||
&:hover
|
||||
color #444
|
||||
|
||||
&:active
|
||||
color #333
|
||||
|
||||
> div:last-child
|
||||
margin 0 0 0 auto
|
||||
|
||||
> footer
|
||||
background #fff
|
||||
|
||||
*
|
||||
color #fff !important
|
||||
text-shadow 0 0 8px #000
|
||||
font-weight bold
|
||||
|
||||
> div
|
||||
max-width $width
|
||||
margin 0 auto
|
||||
padding 80px 0 0 0
|
||||
padding 16px 0
|
||||
text-align center
|
||||
border-top solid 1px #fff
|
||||
|
||||
> div:first-child
|
||||
margin 0 auto 0 0
|
||||
width calc(100% - 500px)
|
||||
color #777
|
||||
|
||||
> h1
|
||||
margin 0
|
||||
font-weight normal
|
||||
font-variant small-caps
|
||||
letter-spacing 12px
|
||||
|
||||
> p
|
||||
margin 0.5em 0
|
||||
line-height 2em
|
||||
|
||||
button
|
||||
padding 8px 16px
|
||||
font-size inherit
|
||||
|
||||
.signup
|
||||
color $theme-color
|
||||
border solid 2px $theme-color
|
||||
border-radius 4px
|
||||
|
||||
&:focus
|
||||
box-shadow 0 0 0 3px rgba($theme-color, 0.2)
|
||||
|
||||
&:hover
|
||||
color $theme-color-foreground
|
||||
background $theme-color
|
||||
|
||||
&:active
|
||||
color $theme-color-foreground
|
||||
background darken($theme-color, 10%)
|
||||
border-color darken($theme-color, 10%)
|
||||
|
||||
.signin
|
||||
&:focus
|
||||
color #444
|
||||
|
||||
&:hover
|
||||
color #444
|
||||
|
||||
&:active
|
||||
color #333
|
||||
|
||||
> div:last-child
|
||||
margin 0 0 0 auto
|
||||
|
||||
> footer
|
||||
background #fff
|
||||
|
||||
*
|
||||
color #fff !important
|
||||
text-shadow 0 0 8px #000
|
||||
font-weight bold
|
||||
|
||||
> div
|
||||
max-width $width
|
||||
margin 0 auto
|
||||
padding 16px 0
|
||||
text-align center
|
||||
border-top solid 1px #fff
|
||||
|
||||
> .c
|
||||
margin 0
|
||||
line-height 64px
|
||||
font-size 10px
|
||||
> .c
|
||||
margin 0
|
||||
line-height 64px
|
||||
font-size 10px
|
||||
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
export default Vue.extend({
|
||||
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -9,8 +9,10 @@ declare const _HOST_: string;
|
|||
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import VModal from 'vue-js-modal';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(VModal);
|
||||
|
||||
import App from './app.vue';
|
||||
|
||||
|
|
Loading…
Reference in a new issue