summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2018-06-15 07:58:58 +0900
committerGitHub <noreply@github.com>2018-06-15 07:58:58 +0900
commit39b1978ff358986281f4abc28e31a1ded91fe990 (patch)
tree4ea8a507adb275b6a29890fe8502c8e0ce6759e2 /src
parent2.38.3 (diff)
parentwip (diff)
downloadsharkey-39b1978ff358986281f4abc28e31a1ded91fe990.tar.gz
sharkey-39b1978ff358986281f4abc28e31a1ded91fe990.tar.bz2
sharkey-39b1978ff358986281f4abc28e31a1ded91fe990.zip
Merge pull request #1713 from syuilo/without-vue-material
Without vue material
Diffstat (limited to 'src')
-rw-r--r--src/client/app/app.styl5
-rw-r--r--src/client/app/common/views/components/index.ts16
-rw-r--r--src/client/app/common/views/components/signup.vue192
-rw-r--r--src/client/app/common/views/components/ui/button.vue82
-rw-r--r--src/client/app/common/views/components/ui/card.vue46
-rw-r--r--src/client/app/common/views/components/ui/form.vue30
-rw-r--r--src/client/app/common/views/components/ui/input.vue321
-rw-r--r--src/client/app/common/views/components/ui/radio.vue120
-rw-r--r--src/client/app/common/views/components/ui/select.vue215
-rw-r--r--src/client/app/common/views/components/ui/switch.vue135
-rw-r--r--src/client/app/common/views/components/ui/textarea.vue174
-rw-r--r--src/client/app/config.ts4
-rw-r--r--src/client/app/mobile/script.ts17
-rw-r--r--src/client/app/mobile/style.styl3
-rw-r--r--src/client/app/mobile/views/pages/settings.vue192
-rw-r--r--src/client/app/mobile/views/pages/settings/settings.profile.vue100
-rw-r--r--src/client/app/mobile/views/pages/signup.vue43
-rw-r--r--src/client/app/mobile/views/pages/welcome.vue183
-rw-r--r--src/client/app/mobile/views/widgets/profile.vue2
-rw-r--r--src/client/md.scss13
-rw-r--r--src/config/types.ts2
21 files changed, 1379 insertions, 516 deletions
diff --git a/src/client/app/app.styl b/src/client/app/app.styl
index ba694b73ae..431b9daa65 100644
--- a/src/client/app/app.styl
+++ b/src/client/app/app.styl
@@ -7,11 +7,6 @@ html
cursor progress !important
body
- // for md
- font-size 16px !important
- line-height initial !important
- letter-spacing initial !important
-
overflow-wrap break-word
#error
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index 803854468e..b91008f718 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -29,6 +29,14 @@ import fileTypeIcon from './file-type-icon.vue';
import Switch from './switch.vue';
import Othello from './othello.vue';
import welcomeTimeline from './welcome-timeline.vue';
+import uiInput from './ui/input.vue';
+import uiButton from './ui/button.vue';
+import uiCard from './ui/card.vue';
+import uiForm from './ui/form.vue';
+import uiTextarea from './ui/textarea.vue';
+import uiSwitch from './ui/switch.vue';
+import uiRadio from './ui/radio.vue';
+import uiSelect from './ui/select.vue';
Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-menu', menu);
@@ -59,3 +67,11 @@ Vue.component('mk-file-type-icon', fileTypeIcon);
Vue.component('mk-switch', Switch);
Vue.component('mk-othello', Othello);
Vue.component('mk-welcome-timeline', welcomeTimeline);
+Vue.component('ui-input', uiInput);
+Vue.component('ui-button', uiButton);
+Vue.component('ui-card', uiCard);
+Vue.component('ui-form', uiForm);
+Vue.component('ui-textarea', uiTextarea);
+Vue.component('ui-switch', uiSwitch);
+Vue.component('ui-radio', uiRadio);
+Vue.component('ui-select', uiSelect);
diff --git a/src/client/app/common/views/components/signup.vue b/src/client/app/common/views/components/signup.vue
index f8bf7dd798..987cc7e52d 100644
--- a/src/client/app/common/views/components/signup.vue
+++ b/src/client/app/common/views/components/signup.vue
@@ -1,60 +1,58 @@
<template>
-<form class="mk-signup" @submit.prevent="onSubmit" autocomplete="off">
- <label class="username">
- <p class="caption">%fa:at%%i18n:@username%</p>
- <input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required @input="onChangeUsername"/>
- <p class="profile-page-url-preview" v-if="shouldShowProfileUrl">{{ `${url}/@${username}` }}</p>
- <p class="info" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%%i18n:@checking%</p>
- <p class="info" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw%%i18n:@available%</p>
- <p class="info" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@unavailable%</p>
- <p class="info" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@error%</p>
- <p class="info" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@invalid-format%</p>
- <p class="info" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@too-short%</p>
- <p class="info" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@too-long%</p>
- </label>
- <label class="password">
- <p class="caption">%fa:lock%%i18n:@password%</p>
- <input v-model="password" type="password" placeholder="%i18n:@password-placeholder%" autocomplete="off" required @input="onChangePassword"/>
- <div class="meter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
- <div class="value" ref="passwordMetar"></div>
+<form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
+ <ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" required @input="onChangeUsername">
+ <span>%i18n:@username%</span>
+ <span slot="prefix">@</span>
+ <span slot="suffix">@{{ host }}</span>
+ <p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p>
+ <p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p>
+ <p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p>
+ <p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</p>
+ <p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@invalid-format%</p>
+ <p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
+ <p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
+ </ui-input>
+ <ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
+ <span>%i18n:@password%</span>
+ <span slot="prefix">%fa:lock%</span>
+ <div slot="text">
+ <p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p>
+ <p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p>
+ <p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
</div>
- <p class="info" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@weak-password%</p>
- <p class="info" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw%%i18n:@normal-password%</p>
- <p class="info" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw%%i18n:@strong-password%</p>
- </label>
- <label class="retype-password">
- <p class="caption">%fa:lock%%i18n:@password%(%i18n:@retype%)</p>
- <input v-model="retypedPassword" type="password" placeholder="%i18n:@retype-placeholder%" autocomplete="off" required @input="onChangePasswordRetype"/>
- <p class="info" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw%%i18n:@password-matched%</p>
- <p class="info" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@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:@recaptcha%</p>
- <div 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/>
+ </ui-input>
+ <ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
+ <span>%i18n:@password% (%i18n:@retype%)</span>
+ <span slot="prefix">%fa:lock%</span>
+ <div slot="text">
+ <p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p>
+ <p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
+ </div>
+ </ui-input>
+ <div class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div>
+ <label class="agree-tou" style="display: block; margin: 16px 0;">
+ <input name="agree-tou" type="checkbox" required/>
<p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
</label>
- <button type="submit">%i18n:@create%</button>
+ <ui-button type="submit">%i18n:@create%</ui-button>
</form>
</template>
<script lang="ts">
import Vue from 'vue';
const getPasswordStrength = require('syuilo-password-strength');
-import { url, docsUrl, lang, recaptchaSitekey } from '../../../config';
+import { host, url, docsUrl, lang, recaptchaSitekey } from '../../../config';
export default Vue.extend({
data() {
return {
+ host,
username: '',
password: '',
retypedPassword: '',
url,
touUrl: `${docsUrl}/${lang}/tou`,
recaptchaSitekey,
- recaptchaed: false,
usernameState: null,
passwordStrength: '',
passwordRetypeState: null
@@ -104,7 +102,6 @@ export default Vue.extend({
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 == '') {
@@ -130,19 +127,9 @@ export default Vue.extend({
alert('%i18n:@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');
@@ -158,100 +145,6 @@ export default Vue.extend({
.mk-signup
min-width 302px
- label
- display block
- margin 0 0 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(#000, 0.1)
- border-radius 4px
- box-shadow 0 0 0 114514px #fff inset
- transition all .3s ease
-
- &:hover
- border-color rgba(#000, 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
@@ -269,19 +162,4 @@ export default Vue.extend({
display inline
color #555
- button
- margin 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>
diff --git a/src/client/app/common/views/components/ui/button.vue b/src/client/app/common/views/components/ui/button.vue
new file mode 100644
index 0000000000..e778750354
--- /dev/null
+++ b/src/client/app/common/views/components/ui/button.vue
@@ -0,0 +1,82 @@
+<template>
+<div class="ui-button" :class="[styl]">
+ <button :type="type" @click="$emit('click')">
+ <slot></slot>
+ </button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ props: {
+ type: {
+ type: String,
+ required: false
+ }
+ },
+ data() {
+ return {
+ styl: 'fill'
+ };
+ },
+ inject: {
+ isCardChild: { default: false }
+ },
+ created() {
+ if (this.isCardChild) {
+ this.styl = 'line';
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark, fill)
+ > button
+ display block
+ width 100%
+ margin 0
+ padding 0
+ font-weight bold
+ font-size 16px
+ line-height 44px
+ border none
+ border-radius 6px
+ outline none
+ box-shadow none
+
+ if fill
+ color $theme-color-foreground
+ background $theme-color
+
+ &:hover
+ background lighten($theme-color, 5%)
+
+ &:active
+ background darken($theme-color, 5%)
+ else
+ color $theme-color
+ background none
+
+ &:hover
+ color darken($theme-color, 5%)
+
+ &:active
+ background rgba($theme-color, 0.3)
+
+.ui-button[data-darkmode]
+ &.fill
+ root(true, true)
+ &:not(.fill)
+ root(true, false)
+
+.ui-button:not([data-darkmode])
+ &.fill
+ root(false, true)
+ &:not(.fill)
+ root(false, false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/card.vue b/src/client/app/common/views/components/ui/card.vue
new file mode 100644
index 0000000000..05c51bca6b
--- /dev/null
+++ b/src/client/app/common/views/components/ui/card.vue
@@ -0,0 +1,46 @@
+<template>
+<div class="ui-card">
+ <header>
+ <slot name="title"></slot>
+ </header>
+
+ <slot></slot>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ provide() {
+ return {
+ isCardChild: true
+ };
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ margin 16px
+ padding 16px
+ color isDark ? #fff : #000
+ background isDark ? #282C37 : #fff
+ box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
+
+ @media (min-width 500px)
+ padding 32px
+
+ > header
+ font-weight normal
+ font-size 24px
+ color isDark ? #fff : #444
+
+.ui-card[data-darkmode]
+ root(true)
+
+.ui-card:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/form.vue b/src/client/app/common/views/components/ui/form.vue
new file mode 100644
index 0000000000..fc8fdad9c4
--- /dev/null
+++ b/src/client/app/common/views/components/ui/form.vue
@@ -0,0 +1,30 @@
+<template>
+<div class="ui-form">
+ <fieldset :disabled="disabled">
+ <slot></slot>
+ </fieldset>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ props: {
+ disabled: {
+ type: Boolean,
+ required: false
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+.ui-form
+ > fieldset
+ margin 0
+ padding 0
+ border none
+
+</style>
diff --git a/src/client/app/common/views/components/ui/input.vue b/src/client/app/common/views/components/ui/input.vue
new file mode 100644
index 0000000000..ec91ca364c
--- /dev/null
+++ b/src/client/app/common/views/components/ui/input.vue
@@ -0,0 +1,321 @@
+<template>
+<div class="ui-input" :class="[{ focused, filled }, styl]">
+ <div class="icon" ref="icon"><slot name="icon"></slot></div>
+ <div class="input" @click="focus" @mousedown="focus">
+ <div class="password-meter" v-if="withPasswordMeter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
+ <div class="value" ref="passwordMetar"></div>
+ </div>
+ <span class="label" ref="label"><slot></slot></span>
+ <div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
+ <template v-if="type != 'file'">
+ <input ref="input"
+ :type="type"
+ :value="v"
+ :required="required"
+ :readonly="readonly"
+ :pattern="pattern"
+ :autocomplete="autocomplete"
+ @input="$emit('input', $event.target.value)"
+ @focus="focused = true"
+ @blur="focused = false">
+ </template>
+ <template v-else>
+ <input ref="input"
+ type="text"
+ :value="placeholder"
+ readonly
+ @click="chooseFile">
+ <input ref="file"
+ type="file"
+ :value="value"
+ @change="onChangeFile">
+ </template>
+ <div class="suffix"><slot name="suffix"></slot></div>
+ </div>
+ <div class="text"><slot name="text"></slot></div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+const getPasswordStrength = require('syuilo-password-strength');
+
+export default Vue.extend({
+ props: {
+ value: {
+ required: false
+ },
+ type: {
+ type: String,
+ required: false
+ },
+ required: {
+ type: Boolean,
+ required: false
+ },
+ readonly: {
+ type: Boolean,
+ required: false
+ },
+ pattern: {
+ type: String,
+ required: false
+ },
+ autocomplete: {
+ required: false
+ },
+ withPasswordMeter: {
+ type: Boolean,
+ required: false,
+ default: false
+ }
+ },
+ data() {
+ return {
+ v: this.value,
+ focused: false,
+ passwordStrength: '',
+ styl: 'fill'
+ };
+ },
+ computed: {
+ filled(): boolean {
+ return this.v != '' && this.v != null;
+ },
+ placeholder(): string {
+ if (this.type != 'file') return null;
+ if (this.v == null) return null;
+
+ if (typeof this.v == 'string') return this.v;
+
+ if (Array.isArray(this.v)) {
+ return this.v.map(file => file.name).join(', ');
+ } else {
+ return this.v.name;
+ }
+ }
+ },
+ watch: {
+ value(v) {
+ this.v = v;
+ },
+ v(v) {
+ if (this.withPasswordMeter) {
+ if (v == '') {
+ this.passwordStrength = '';
+ return;
+ }
+
+ const strength = getPasswordStrength(v);
+ this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
+ (this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
+ }
+ }
+ },
+ inject: {
+ isCardChild: { default: false }
+ },
+ created() {
+ if (this.isCardChild) {
+ this.styl = 'line';
+ }
+ },
+ mounted() {
+ if (this.$refs.prefix) {
+ this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
+ }
+ },
+ methods: {
+ focus() {
+ this.$refs.input.focus();
+ },
+ chooseFile() {
+ this.$refs.file.click();
+ },
+ onChangeFile() {
+ this.v = Array.from((this.$refs.file as any).files);
+ this.$emit('input', this.v);
+ this.$emit('change', this.v);
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark, fill)
+ margin 32px 0
+
+ > .icon
+ position absolute
+ top 0
+ left 0
+ width 24px
+ text-align center
+ line-height 32px
+ color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+
+ &:not(:empty) + .input
+ margin-left 28px
+
+ > .input
+ display flex
+ cursor text
+
+ if fill
+ padding 6px 12px
+ background rgba(#000, 0.035)
+ border-radius 6px
+ else
+ &:before
+ content ''
+ display block
+ position absolute
+ bottom 0
+ left 0
+ right 0
+ height 1px
+ background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
+
+ &:after
+ content ''
+ display block
+ position absolute
+ bottom 0
+ left 0
+ right 0
+ height 2px
+ background $theme-color
+ opacity 0
+ transform scaleX(0.12)
+ transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
+ will-change border opacity transform
+
+ > .password-meter
+ position absolute
+ top 0
+ left 0
+ width 100%
+ height 100%
+ border-radius 6px
+ overflow hidden
+ opacity 0.3
+
+ &[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 6px
+ transition all 0.1s ease
+
+ > .label
+ position absolute
+ top fill ? 6px : 0
+ left 0
+ pointer-events none
+ transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
+ transition-duration 0.3s
+ font-size 16px
+ line-height 32px
+ color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+ pointer-events none
+ //will-change transform
+ transform-origin top left
+ transform scale(1)
+
+ > input
+ display block
+ flex 1
+ width 100%
+ padding 0
+ font inherit
+ font-weight fill ? bold : normal
+ font-size 16px
+ line-height 32px
+ color isDark ? #fff : #000
+ background transparent
+ border none
+ border-radius 0
+ outline none
+ box-shadow none
+
+ &[type='file']
+ display none
+
+ > .prefix
+ > .suffix
+ display block
+ align-self center
+ justify-self center
+ font-size 16px
+ line-height 32px
+ color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+ pointer-events none
+
+ > *
+ display block
+ min-width 16px
+
+ > .prefix
+ padding-right 4px
+
+ > .suffix
+ padding-left 4px
+
+ > .text
+ margin 6px 0
+ font-size 13px
+
+ *
+ margin 0
+
+ &.focused
+ > .input
+ if fill
+ background rgba(#000, 0.05)
+ else
+ &:after
+ opacity 1
+ transform scaleX(1)
+
+ > .label
+ color $theme-color
+
+ &.focused
+ &.filled
+ > .input
+ > .label
+ top fill ? -24px : -17px
+ left 0 !important
+ transform scale(0.75)
+
+.ui-input[data-darkmode]
+ &.fill
+ root(true, true)
+ &:not(.fill)
+ root(true, false)
+
+.ui-input:not([data-darkmode])
+ &.fill
+ root(false, true)
+ &:not(.fill)
+ root(false, false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/radio.vue b/src/client/app/common/views/components/ui/radio.vue
new file mode 100644
index 0000000000..04a46c5a96
--- /dev/null
+++ b/src/client/app/common/views/components/ui/radio.vue
@@ -0,0 +1,120 @@
+<template>
+<div
+ class="ui-radio"
+ :class="{ disabled, checked }"
+ :aria-checked="checked"
+ :aria-disabled="disabled"
+ @click="toggle"
+>
+ <input type="radio"
+ :disabled="disabled"
+ >
+ <span class="button">
+ <span></span>
+ </span>
+ <span class="label"><slot></slot></span>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ model: {
+ prop: 'model',
+ event: 'change'
+ },
+ props: {
+ model: {
+ type: String,
+ required: false
+ },
+ value: {
+ type: String,
+ required: false
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ }
+ },
+ computed: {
+ checked(): boolean {
+ return this.model === this.value;
+ }
+ },
+ methods: {
+ toggle() {
+ this.$emit('change', this.value);
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ display inline-block
+ margin 32px 32px 32px 0
+ cursor pointer
+ transition all 0.3s
+
+ > *
+ user-select none
+
+ &.disabled
+ opacity 0.6
+ cursor not-allowed
+
+ &.checked
+ > .button
+ border-color $theme-color
+
+ &:after
+ background-color $theme-color
+ transform scale(1)
+ opacity 1
+
+ > input
+ position absolute
+ width 0
+ height 0
+ opacity 0
+ margin 0
+
+ > .button
+ position absolute
+ width 20px
+ height 20px
+ background none
+ border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+ border-radius 100%
+ transition inherit
+
+ &:after
+ content ''
+ display block
+ position absolute
+ top 3px
+ right 3px
+ bottom 3px
+ left 3px
+ border-radius 100%
+ opacity 0
+ transform scale(0)
+ transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
+
+ > .label
+ margin-left 28px
+ display block
+ font-size 16px
+ line-height 20px
+ cursor pointer
+
+.ui-radio[data-darkmode]
+ root(true)
+
+.ui-radio:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/select.vue b/src/client/app/common/views/components/ui/select.vue
new file mode 100644
index 0000000000..4273a4a0de
--- /dev/null
+++ b/src/client/app/common/views/components/ui/select.vue
@@ -0,0 +1,215 @@
+<template>
+<div class="ui-select" :class="[{ focused, filled }, styl]">
+ <div class="icon" ref="icon"><slot name="icon"></slot></div>
+ <div class="input" @click="focus">
+ <span class="label" ref="label"><slot name="label"></slot></span>
+ <div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
+ <select ref="input"
+ :value="v"
+ :required="required"
+ @input="$emit('input', $event.target.value)"
+ @focus="focused = true"
+ @blur="focused = false">
+ <slot></slot>
+ </select>
+ <div class="suffix"><slot name="suffix"></slot></div>
+ </div>
+ <div class="text"><slot name="text"></slot></div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+ props: {
+ value: {
+ required: false
+ },
+ required: {
+ type: Boolean,
+ required: false
+ }
+ },
+ data() {
+ return {
+ v: this.value,
+ focused: false,
+ styl: 'fill'
+ };
+ },
+ computed: {
+ filled(): boolean {
+ return this.v != '' && this.v != null;
+ }
+ },
+ watch: {
+ value(v) {
+ this.v = v;
+ }
+ },
+ inject: {
+ isCardChild: { default: false }
+ },
+ created() {
+ if (this.isCardChild) {
+ this.styl = 'line';
+ }
+ },
+ mounted() {
+ if (this.$refs.prefix) {
+ this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
+ }
+ },
+ methods: {
+ focus() {
+ this.$refs.input.focus();
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark, fill)
+ margin 32px 0
+
+ > .icon
+ position absolute
+ top 0
+ left 0
+ width 24px
+ text-align center
+ line-height 32px
+ color rgba(#000, 0.54)
+
+ &:not(:empty) + .input
+ margin-left 28px
+
+ > .input
+ display flex
+
+ if fill
+ padding 6px 12px
+ background rgba(#000, 0.035)
+ border-radius 6px
+ else
+ &:before
+ content ''
+ display block
+ position absolute
+ bottom 0
+ left 0
+ right 0
+ height 1px
+ background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
+
+ &:after
+ content ''
+ display block
+ position absolute
+ bottom 0
+ left 0
+ right 0
+ height 2px
+ background $theme-color
+ opacity 0
+ transform scaleX(0.12)
+ transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
+ will-change border opacity transform
+
+ > .label
+ position absolute
+ top fill ? 6px : 0
+ left 0
+ pointer-events none
+ transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
+ transition-duration 0.3s
+ font-size 16px
+ line-height 32px
+ color rgba(#000, 0.54)
+ pointer-events none
+ //will-change transform
+ transform-origin top left
+ transform scale(1)
+
+ > select
+ display block
+ flex 1
+ width 100%
+ padding 0
+ font inherit
+ font-weight fill ? bold : normal
+ font-size 16px
+ height 32px
+ color isDark ? #fff : #000
+ background transparent
+ border none
+ border-radius 0
+ outline none
+ box-shadow none
+
+ *
+ color #000
+
+ > .prefix
+ > .suffix
+ display block
+ align-self center
+ justify-self center
+ font-size 16px
+ line-height 32px
+ color rgba(#000, 0.54)
+ pointer-events none
+
+ > *
+ display block
+ min-width 16px
+
+ > .prefix
+ padding-right 4px
+
+ > .suffix
+ padding-left 4px
+
+ > .text
+ margin 6px 0
+ font-size 13px
+
+ *
+ margin 0
+
+ &.focused
+ > .input
+ if fill
+ background rgba(#000, 0.05)
+ else
+ &:after
+ opacity 1
+ transform scaleX(1)
+
+ > .label
+ color $theme-color
+
+ &.focused
+ &.filled
+ > .input
+ > .label
+ top fill ? -24px : -17px
+ left 0 !important
+ transform scale(0.75)
+
+.ui-select[data-darkmode]
+ &.fill
+ root(true, true)
+ &:not(.fill)
+ root(true, false)
+
+.ui-select:not([data-darkmode])
+ &.fill
+ root(false, true)
+ &:not(.fill)
+ root(false, false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/switch.vue b/src/client/app/common/views/components/ui/switch.vue
new file mode 100644
index 0000000000..a9e00d73d2
--- /dev/null
+++ b/src/client/app/common/views/components/ui/switch.vue
@@ -0,0 +1,135 @@
+<template>
+<div
+ class="ui-switch"
+ :class="{ disabled, checked }"
+ role="switch"
+ :aria-checked="checked"
+ :aria-disabled="disabled"
+ @click="toggle"
+>
+ <input
+ type="checkbox"
+ ref="input"
+ :disabled="disabled"
+ @keydown.enter="toggle"
+ >
+ <span class="button">
+ <span></span>
+ </span>
+ <span class="label">
+ <span :aria-hidden="!checked"><slot></slot></span>
+ <p :aria-hidden="!checked">
+ <slot name="text"></slot>
+ </p>
+ </span>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ model: {
+ prop: 'value',
+ event: 'change'
+ },
+ props: {
+ value: {
+ type: Boolean,
+ default: false
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ }
+ },
+ computed: {
+ checked(): boolean {
+ return this.value;
+ }
+ },
+ methods: {
+ toggle() {
+ this.$emit('change', !this.checked);
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ display flex
+ margin 32px 0
+ cursor pointer
+ transition all 0.3s
+
+ > *
+ user-select none
+
+ &.disabled
+ opacity 0.6
+ cursor not-allowed
+
+ &.checked
+ > .button
+ background-color rgba($theme-color, 0.4)
+ border-color rgba($theme-color, 0.4)
+
+ > *
+ background-color $theme-color
+ transform translateX(14px)
+
+ > input
+ position absolute
+ width 0
+ height 0
+ opacity 0
+ margin 0
+
+ > .button
+ display inline-block
+ margin 3px 0 0 0
+ width 34px
+ height 14px
+ background isDark ? rgba(#fff, 0.15) : rgba(#000, 0.25)
+ outline none
+ border-radius 14px
+ transition inherit
+
+ > *
+ position absolute
+ top -3px
+ left 0
+ border-radius 100%
+ transition background-color 0.3s, transform 0.3s
+ width 20px
+ height 20px
+ background-color #fff
+ box-shadow 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12)
+
+ > .label
+ margin-left 8px
+ display block
+ font-size 16px
+ cursor pointer
+ transition inherit
+
+ > span
+ display block
+ line-height 20px
+ color isDark ? #c4ccd2 : rgba(#000, 0.75)
+ transition inherit
+
+ > p
+ margin 0
+ //font-size 90%
+ color isDark ? #78858e : #9daab3
+
+.ui-switch[data-darkmode]
+ root(true)
+
+.ui-switch:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/textarea.vue b/src/client/app/common/views/components/ui/textarea.vue
new file mode 100644
index 0000000000..cc6b376ead
--- /dev/null
+++ b/src/client/app/common/views/components/ui/textarea.vue
@@ -0,0 +1,174 @@
+<template>
+<div class="ui-textarea" :class="{ focused, filled }">
+ <div class="input">
+ <span class="label" ref="label"><slot></slot></span>
+ <textarea ref="input"
+ :value="value"
+ :required="required"
+ :readonly="readonly"
+ :pattern="pattern"
+ :autocomplete="autocomplete"
+ @input="$emit('input', $event.target.value)"
+ @focus="focused = true"
+ @blur="focused = false">
+ </textarea>
+ </div>
+ <div class="text"><slot name="text"></slot></div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+const getPasswordStrength = require('syuilo-password-strength');
+
+export default Vue.extend({
+ props: {
+ value: {
+ required: false
+ },
+ required: {
+ type: Boolean,
+ required: false
+ },
+ readonly: {
+ type: Boolean,
+ required: false
+ },
+ pattern: {
+ type: String,
+ required: false
+ },
+ autocomplete: {
+ type: String,
+ required: false
+ }
+ },
+ data() {
+ return {
+ focused: false,
+ passwordStrength: ''
+ }
+ },
+ computed: {
+ filled(): boolean {
+ return this.value != '' && this.value != null;
+ }
+ },
+ methods: {
+ focus() {
+ this.$refs.input.focus();
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark, fill)
+ margin 32px 0
+
+ > .input
+ padding 12px
+
+ if fill
+ background rgba(#000, 0.035)
+ border-radius 6px
+ else
+ &:before
+ content ''
+ display block
+ position absolute
+ top 0
+ bottom 0
+ left 0
+ right 0
+ background none
+ border solid 1px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
+ border-radius 3px
+ pointer-events none
+
+ &:after
+ content ''
+ display block
+ position absolute
+ top 0
+ bottom 0
+ left 0
+ right 0
+ background none
+ border solid 2px $theme-color
+ border-radius 3px
+ opacity 0
+ transition opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)
+ pointer-events none
+
+ > .label
+ position absolute
+ top 6px
+ left 12px
+ pointer-events none
+ transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
+ transition-duration 0.3s
+ font-size 16px
+ line-height 32px
+ color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+ pointer-events none
+ //will-change transform
+ transform-origin top left
+ transform scale(1)
+
+ > textarea
+ display block
+ width 100%
+ min-height 100px
+ padding 0
+ font inherit
+ font-weight fill ? bold : normal
+ font-size 16px
+ color isDark ? #fff : #000
+ background transparent
+ border none
+ border-radius 0
+ outline none
+ box-shadow none
+
+ > .text
+ margin 6px 0
+ font-size 13px
+
+ *
+ margin 0
+
+ &.focused
+ > .input
+ if fill
+ background rgba(#000, 0.05)
+ else
+ &:after
+ opacity 1
+
+ > .label
+ color $theme-color
+
+ &.focused
+ &.filled
+ > .input
+ > .label
+ top -24px
+ left 0 !important
+ transform scale(0.75)
+
+.ui-textarea[data-darkmode]
+ &.fill
+ root(true, true)
+ &:not(.fill)
+ root(true, false)
+
+.ui-textarea:not([data-darkmode])
+ &.fill
+ root(false, true)
+ &:not(.fill)
+ root(false, false)
+
+</style>
diff --git a/src/client/app/config.ts b/src/client/app/config.ts
index 70c085de1c..e4a7ff6d38 100644
--- a/src/client/app/config.ts
+++ b/src/client/app/config.ts
@@ -1,6 +1,8 @@
declare const _HOST_: string;
declare const _HOSTNAME_: string;
declare const _URL_: string;
+declare const _NAME_: string;
+declare const _DESCRIPTION_: string;
declare const _API_URL_: string;
declare const _WS_URL_: string;
declare const _DOCS_URL_: string;
@@ -21,6 +23,8 @@ declare const _GOOGLE_MAPS_API_KEY_: string;
export const host = _HOST_;
export const hostname = _HOSTNAME_;
export const url = _URL_;
+export const name = _NAME_;
+export const description = _DESCRIPTION_;
export const apiUrl = _API_URL_;
export const wsUrl = _WS_URL_;
export const docsUrl = _DOCS_URL_;
diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts
index d505b38dcc..8ee078d621 100644
--- a/src/client/app/mobile/script.ts
+++ b/src/client/app/mobile/script.ts
@@ -2,17 +2,11 @@
* Mobile Client
*/
-import Vue from 'vue';
import VueRouter from 'vue-router';
-import { MdCard, MdButton, MdField, MdMenu, MdList, MdSwitch, MdSubheader, MdDialog, MdDialogAlert, MdRadio } from 'vue-material/dist/components';
-import 'vue-material/dist/vue-material.min.css';
-import 'vue-material/dist/theme/default.css';
-
// Style
import './style.styl';
import '../../element.scss';
-import '../../md.scss';
import init from '../init';
@@ -44,17 +38,6 @@ import MkSettings from './views/pages/settings.vue';
import MkOthello from './views/pages/othello.vue';
import MkTag from './views/pages/tag.vue';
-Vue.use(MdCard);
-Vue.use(MdButton);
-Vue.use(MdField);
-Vue.use(MdMenu);
-Vue.use(MdList);
-Vue.use(MdSwitch);
-Vue.use(MdSubheader);
-Vue.use(MdDialog);
-Vue.use(MdDialogAlert);
-Vue.use(MdRadio);
-
/**
* init
*/
diff --git a/src/client/app/mobile/style.styl b/src/client/app/mobile/style.styl
index d1ab044eaf..df8f4a8fae 100644
--- a/src/client/app/mobile/style.styl
+++ b/src/client/app/mobile/style.styl
@@ -10,9 +10,6 @@ html
height 100%
background #ececed !important
- // for md
- transition none !important
-
&[data-darkmode]
background #191B22 !important
diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue
index 8da7a76633..1c5a43ede4 100644
--- a/src/client/app/mobile/views/pages/settings.vue
+++ b/src/client/app/mobile/views/pages/settings.vue
@@ -1,132 +1,84 @@
<template>
<mk-ui>
<span slot="header">%fa:cog%%i18n:@settings%</span>
- <main>
- <p v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p>
+ <main :data-darkmode="$store.state.device.darkmode">
+ <div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></div>
+
<div>
<x-profile/>
- <md-card>
- <md-card-header>
- <div class="md-title">%fa:palette% %i18n:@design%</div>
- </md-card-header>
-
- <md-card-content>
- <div>
- <md-switch v-model="darkmode">%i18n:@dark-mode%</md-switch>
- </div>
-
- <div>
- <md-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</md-switch>
- </div>
-
- <div>
- <div class="md-body-2">%i18n:@timeline%</div>
-
- <div>
- <md-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</md-switch>
- </div>
-
- <div>
- <md-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</md-switch>
- </div>
-
- <div>
- <md-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</md-switch>
- </div>
- </div>
+ <ui-card>
+ <div slot="title">%fa:palette% %i18n:@design%</div>
- <div>
- <div class="md-body-2">%i18n:@post-style%</div>
+ <ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
+ <ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
- <md-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</md-radio>
- <md-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</md-radio>
- </div>
- </md-card-content>
- </md-card>
+ <div>
+ <div>%i18n:@timeline%</div>
+ <ui-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</ui-switch>
+ <ui-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</ui-switch>
+ <ui-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
+ </div>
- <md-card>
- <md-card-header>
- <div class="md-title">%fa:cog% %i18n:@behavior%</div>
- </md-card-header>
+ <div>
+ <div>%i18n:@post-style%</div>
+ <ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio>
+ <ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio>
+ </div>
+ </ui-card>
- <md-card-content>
- <div>
- <md-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</md-switch>
- </div>
+ <ui-card>
+ <div slot="title">%fa:cog% %i18n:@behavior%</div>
+ <ui-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
+ <ui-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
+ <ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
+ <ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
+ <ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
+ </ui-card>
- <div>
- <md-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</md-switch>
- </div>
+ <ui-card>
+ <div slot="title">%fa:language% %i18n:@lang%</div>
- <div>
- <md-switch v-model="loadRawImages">%i18n:@load-raw-images%</md-switch>
- </div>
+ <ui-select v-model="lang" placeholder="%i18n:@auto%">
+ <optgroup label="%i18n:@recommended%">
+ <option value="">%i18n:@auto%</option>
+ </optgroup>
- <div>
- <md-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</md-switch>
- </div>
+ <optgroup label="%i18n:@specify-language%">
+ <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
+ </optgroup>
+ </ui-select>
+ <span>%fa:info-circle% %i18n:@lang-tip%</span>
+ </ui-card>
- <div>
- <md-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</md-switch>
- </div>
- </md-card-content>
- </md-card>
+ <ui-card>
+ <div slot="title">%fa:B twitter% %i18n:@twitter%</div>
- <md-card>
- <md-card-header>
- <div class="md-title">%fa:language% %i18n:@lang%</div>
- </md-card-header>
+ <p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
+ <p>
+ <a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
+ <span v-if="$store.state.i.twitter"> or </span>
+ <a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
+ </p>
+ </ui-card>
- <md-card-content>
- <md-field>
- <md-select v-model="lang" placeholder="%i18n:@auto%">
- <md-optgroup label="%i18n:@recommended%">
- <md-option value="">%i18n:@auto%</md-option>
- </md-optgroup>
+ <ui-card>
+ <div slot="title">%fa:sync-alt% %i18n:@update%</div>
- <md-optgroup label="%i18n:@specify-language%">
- <md-option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</md-option>
- </md-optgroup>
- </md-select>
- </md-field>
- <span class="md-helper-text">%fa:info-circle% %i18n:@lang-tip%</span>
- </md-card-content>
- </md-card>
-
- <md-card>
- <md-card-header>
- <div class="md-title">%fa:B twitter% %i18n:@twitter%</div>
- </md-card-header>
-
- <md-card-content>
- <p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
- <p>
- <a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
- <span v-if="$store.state.i.twitter"> or </span>
- <a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
- </p>
- </md-card-content>
- </md-card>
-
- <md-card>
- <md-card-header>
- <div class="md-title">%fa:sync-alt% %i18n:@update%</div>
- </md-card-header>
-
- <md-card-content>
- <div>%i18n:@version% <i>{{ version }}</i></div>
- <template v-if="latestVersion !== undefined">
- <div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
- </template>
- <md-button class="md-raised md-primary" @click="checkForUpdate" :disabled="checkingForUpdate">
- <template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
- <template v-else>%i18n:@check-for-updates%</template>
- </md-button>
- </md-card-content>
- </md-card>
+ <div>%i18n:@version% <i>{{ version }}</i></div>
+ <template v-if="latestVersion !== undefined">
+ <div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
+ </template>
+ <ui-button @click="checkForUpdate" :disabled="checkingForUpdate">
+ <template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
+ <template v-else>%i18n:@check-for-updates%</template>
+ </ui-button>
+ </ui-card>
</div>
- <p><small>ver {{ version }} ({{ codename }})</small></p>
+
+ <footer>
+ <small>ver {{ version }} ({{ codename }})</small>
+ </footer>
</main>
</mk-ui>
</template>
@@ -267,20 +219,22 @@ export default Vue.extend({
<style lang="stylus" scoped>
root(isDark)
- padding 0 16px
margin 0 auto
max-width 500px
width 100%
- > div
- > *
- margin-bottom 16px
+ > .signin-as
+ margin 16px
+ padding 16px
+ text-align center
+ color isDark ? #49ab63 : #2c662d
+ background isDark ? #273c34 : #fcfff5
+ box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
- > p
- display block
- margin 24px
+ > footer
+ margin 16px
text-align center
- color isDark ? #cad2da : #a2a9b1
+ color isDark ? #c9d2e0 : #888
main[data-darkmode]
root(true)
diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue
index f3444eb1f0..da97cbebd7 100644
--- a/src/client/app/mobile/views/pages/settings/settings.profile.vue
+++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue
@@ -1,62 +1,49 @@
<template>
- <md-card>
- <md-card-header>
- <div class="md-title">%fa:pencil-alt% %i18n:@title%</div>
- </md-card-header>
+<ui-card>
+ <div slot="title">%fa:user% %i18n:@title%</div>
- <md-card-content>
- <md-field>
- <label>%i18n:@name%</label>
- <md-input v-model="name" :disabled="saving" md-counter="30"/>
- </md-field>
+ <ui-form :disabled="saving">
+ <ui-input v-model="name" :max="30">
+ <span>%i18n:@name%</span>
+ </ui-input>
- <md-field>
- <label>%i18n:@account%</label>
- <span class="md-prefix">@</span>
- <md-input v-model="username" readonly></md-input>
- <span class="md-suffix">@{{ host }}</span>
- </md-field>
+ <ui-input v-model="username" readonly>
+ <span>%i18n:@account%</span>
+ <span slot="prefix">@</span>
+ <span slot="suffix">@{{ host }}</span>
+ </ui-input>
- <md-field>
- <md-icon>%fa:map-marker-alt%</md-icon>
- <label>%i18n:@location%</label>
- <md-input v-model="location" :disabled="saving"/>
- </md-field>
+ <ui-input v-model="location">
+ <span>%i18n:@location%</span>
+ <span slot="prefix">%fa:map-marker-alt%</span>
+ </ui-input>
- <md-field>
- <md-icon>%fa:birthday-cake%</md-icon>
- <label>%i18n:@birthday%</label>
- <md-input type="date" v-model="birthday" :disabled="saving"/>
- </md-field>
+ <ui-input v-model="birthday" type="date">
+ <span>%i18n:@birthday%</span>
+ <span slot="prefix">%fa:birthday-cake%</span>
+ </ui-input>
- <md-field>
- <label>%i18n:@description%</label>
- <md-textarea v-model="description" :disabled="saving" md-counter="500"/>
- </md-field>
+ <ui-textarea v-model="description" :max="500">
+ <span>%i18n:@description%</span>
+ </ui-textarea>
- <md-field>
- <label>%i18n:@avatar%</label>
- <md-file @md-change="onAvatarChange"/>
- </md-field>
+ <ui-input type="file" @change="onAvatarChange">
+ <span>%i18n:@avatar%</span>
+ <span slot="icon">%fa:image%</span>
+ <span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
+ </ui-input>
- <md-field>
- <label>%i18n:@banner%</label>
- <md-file @md-change="onBannerChange"/>
- </md-field>
+ <ui-input type="file" @change="onBannerChange">
+ <span>%i18n:@banner%</span>
+ <span slot="icon">%fa:image%</span>
+ <span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
+ </ui-input>
- <md-dialog-alert
- :md-active.sync="uploading"
- md-content="%18n:!@uploading%"/>
+ <ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch>
- <div>
- <md-switch v-model="isCat">%i18n:@is-cat%</md-switch>
- </div>
- </md-card-content>
-
- <md-card-actions>
- <md-button class="md-primary" :disabled="saving" @click="save">%i18n:@save%</md-button>
- </md-card-actions>
- </md-card>
+ <ui-button @click="save">%i18n:@save%</ui-button>
+ </ui-form>
+</ui-card>
</template>
<script lang="ts">
@@ -77,7 +64,8 @@ export default Vue.extend({
isBot: false,
isCat: false,
saving: false,
- uploading: false
+ avatarUploading: false,
+ bannerUploading: false
};
},
@@ -95,7 +83,7 @@ export default Vue.extend({
methods: {
onAvatarChange([file]) {
- this.uploading = true;
+ this.avatarUploading = true;
const data = new FormData();
data.append('file', file);
@@ -108,16 +96,16 @@ export default Vue.extend({
.then(response => response.json())
.then(f => {
this.avatarId = f.id;
- this.uploading = false;
+ this.avatarUploading = false;
})
.catch(e => {
- this.uploading = false;
+ this.avatarUploading = false;
alert('%18n:!@upload-failed%');
});
},
onBannerChange([file]) {
- this.uploading = true;
+ this.bannerUploading = true;
const data = new FormData();
data.append('file', file);
@@ -130,10 +118,10 @@ export default Vue.extend({
.then(response => response.json())
.then(f => {
this.bannerId = f.id;
- this.uploading = false;
+ this.bannerUploading = false;
})
.catch(e => {
- this.uploading = false;
+ this.bannerUploading = false;
alert('%18n:!@upload-failed%');
});
},
diff --git a/src/client/app/mobile/views/pages/signup.vue b/src/client/app/mobile/views/pages/signup.vue
index b8245beb00..47384e2b3c 100644
--- a/src/client/app/mobile/views/pages/signup.vue
+++ b/src/client/app/mobile/views/pages/signup.vue
@@ -1,57 +1,26 @@
<template>
<div class="signup">
<h1>Misskeyをはじめる</h1>
- <p>いつでも、どこからでもMisskeyを利用できます。もちろん、無料です。</p>
- <div class="form">
- <p>新規登録</p>
- <div>
- <mk-signup/>
- </div>
- </div>
+ <mk-signup/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
-export default Vue.extend({
- mounted() {
- document.documentElement.style.background = '#293946';
- }
-});
+export default Vue.extend({});
</script>
<style lang="stylus" scoped>
.signup
- padding 16px
+ padding 32px
margin 0 auto
max-width 500px
h1
margin 0
- padding 8px
+ padding 8px 0 0 0
font-size 1.5em
- font-weight normal
- color #c3c6ca
-
- & + p
- margin 0 0 16px 0
- padding 0 8px 0 8px
- color #949fa9
-
- .form
- background #fff
- border solid 1px rgba(#000, 0.2)
- border-radius 8px
- overflow hidden
-
- > p
- margin 0
- padding 12px 20px
- color #555
- background #f5f5f5
- border-bottom solid 1px #ddd
-
- > div
- padding 16px
+ font-weight bold
+ color #444
</style>
diff --git a/src/client/app/mobile/views/pages/welcome.vue b/src/client/app/mobile/views/pages/welcome.vue
index 64cfa5a46c..3bf2a0af9f 100644
--- a/src/client/app/mobile/views/pages/welcome.vue
+++ b/src/client/app/mobile/views/pages/welcome.vue
@@ -1,29 +1,31 @@
<template>
<div class="welcome">
<div>
- <h1><b>Misskey</b>へようこそ</h1>
- <p>Twitter風ミニブログSNS、Misskeyへようこそ。共有したいことを投稿したり、タイムラインでみんなの投稿を読むこともできます。<br><a href="/signup">アカウントを作成する</a></p>
- <div class="form">
- <p>%fa:lock% ログイン</p>
+ <img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" alt="Misskey">
+ <p class="host">{{ host }}</p>
+ <div class="about">
+ <h2>{{ name || 'unidentified' }}</h2>
+ <p v-html="description || '%i18n:common.about%'"></p>
+ <router-link class="signup" to="/signup">新規登録</router-link>
+ </div>
+ <div class="login">
+ <form @submit.prevent="onSubmit">
+ <ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" autofocus required @change="onUsernameChange">
+ <span>ユーザー名</span>
+ <span slot="prefix">@</span>
+ <span slot="suffix">@{{ host }}</span>
+ </ui-input>
+ <ui-input v-model="password" type="password" required>
+ <span>パスワード</span>
+ <span slot="prefix">%fa:lock%</span>
+ </ui-input>
+ <ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
+ <ui-button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</ui-button>
+ </form>
<div>
- <form @submit.prevent="onSubmit">
- <input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" placeholder="ユーザー名" autofocus required @change="onUsernameChange"/>
- <input v-model="password" type="password" placeholder="パスワード" required/>
- <input v-if="user && user.twoFactorEnabled" v-model="token" type="number" placeholder="トークン" required/>
- <button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</button>
- </form>
- <div>
- <a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a>
- </div>
+ <a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a>
</div>
</div>
- <div class="tl">
- <p>%fa:comments R% タイムラインを見てみる</p>
- <mk-welcome-timeline/>
- </div>
- <div class="users">
- <mk-avatar class="avatar" v-for="user in users" :key="user.id" :user="user"/>
- </div>
<footer>
<small>{{ copyright }}</small>
</footer>
@@ -33,7 +35,7 @@
<script lang="ts">
import Vue from 'vue';
-import { apiUrl, copyright } from '../../../config';
+import { apiUrl, copyright, host, name, description } from '../../../config';
export default Vue.extend({
data() {
@@ -45,7 +47,10 @@ export default Vue.extend({
token: '',
apiUrl,
copyright,
- users: []
+ users: [],
+ host,
+ name,
+ description
};
},
mounted() {
@@ -84,112 +89,74 @@ export default Vue.extend({
<style lang="stylus" scoped>
.welcome
- background linear-gradient(to bottom, #1e1d65, #bd6659)
+ text-align center
+ //background #fff
> div
- padding 16px
+ padding 32px
margin 0 auto
max-width 500px
- h1
- margin 0
- padding 8px
- font-size 1.5em
- font-weight normal
- color #cacac3
+ > img
+ display block
+ max-width 200px
+ margin 0 auto
- & + p
- margin 0 0 16px 0
- padding 0 8px 0 8px
- color #949fa9
+ > .host
+ display block
+ text-align center
+ padding 6px 12px
+ line-height 32px
+ font-weight bold
+ color #333
+ background rgba(#000, 0.035)
+ border-radius 6px
- .form
- margin-bottom 16px
+ > .about
+ margin-top 16px
+ padding 16px
+ color #555
background #fff
- border solid 1px rgba(#000, 0.2)
- border-radius 8px
- overflow hidden
+ border-radius 6px
- > p
+ > h2
margin 0
- padding 12px 20px
- color #555
- background #f5f5f5
- border-bottom solid 1px #ddd
-
- > div
- > form
- padding 16px
- border-bottom solid 1px #ddd
+ > p
+ margin 8px
- input
- display block
- padding 12px
- margin 0 0 16px 0
- width 100%
- font-size 1em
- color rgba(#000, 0.7)
- background #fff
- outline none
- border solid 1px #ddd
- border-radius 4px
+ > .signup
+ font-weight bold
- button
- display block
- width 100%
- padding 10px
- margin 0
- color #333
- font-size 1em
- text-align center
- text-decoration none
- text-shadow 0 1px 0 rgba(255, 255, 255, 0.9)
- background-image linear-gradient(#fafafa, #eaeaea)
- border 1px solid #ddd
- border-bottom-color #cecece
- border-radius 4px
+ > .login
+ margin 16px 0
- &:active
- background-color #767676
- background-image none
- border-color #444
- box-shadow 0 1px 3px rgba(#000, 0.075), inset 0 0 5px rgba(#000, 0.2)
+ > form
- > div
- padding 16px
+ button
+ display block
+ width 100%
+ padding 10px
+ margin 0
+ color #333
+ font-size 1em
text-align center
+ text-decoration none
+ text-shadow 0 1px 0 rgba(255, 255, 255, 0.9)
+ background-image linear-gradient(#fafafa, #eaeaea)
+ border 1px solid #ddd
+ border-bottom-color #cecece
+ border-radius 4px
- > .tl
- background #fff
- border solid 1px rgba(#000, 0.2)
- border-radius 8px
- overflow hidden
-
- > p
- margin 0
- padding 12px 20px
- color #555
- background #f5f5f5
- border-bottom solid 1px #ddd
-
- > .mk-welcome-timeline
- max-height 300px
- overflow auto
-
- > .users
- margin 12px 0 0 0
-
- > *
- display inline-block
- margin 4px
- width 38px
- height 38px
- border-radius 6px
+ &:active
+ background-color #767676
+ background-image none
+ border-color #444
+ box-shadow 0 1px 3px rgba(#000, 0.075), inset 0 0 5px rgba(#000, 0.2)
> footer
text-align center
- color #fff
+ color #444
> small
display block
diff --git a/src/client/app/mobile/views/widgets/profile.vue b/src/client/app/mobile/views/widgets/profile.vue
index beae1ffa36..a94f7e94b8 100644
--- a/src/client/app/mobile/views/widgets/profile.vue
+++ b/src/client/app/mobile/views/widgets/profile.vue
@@ -56,7 +56,7 @@ export default define({
left 92px
margin 0
line-height 100px
- color #fff !important // !important is for md
+ color #fff
font-weight bold
text-shadow 0 0 8px rgba(#000, 0.5)
diff --git a/src/client/md.scss b/src/client/md.scss
deleted file mode 100644
index 8368365885..0000000000
--- a/src/client/md.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-/* SEE: https://vuematerial.io/themes/configuration */
-
-@import '../const.json';
-
-@import "~vue-material/dist/theme/engine";
-
-@include md-register-theme("default", (
- primary: $themeColor,
- accent: $themeColor
-));
-
-@import "~vue-material/dist/components/MdButton/theme";
-@import "~vue-material/dist/components/MdField/theme";
diff --git a/src/config/types.ts b/src/config/types.ts
index 910c03c2c1..d557f2afde 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -15,6 +15,8 @@ export type Source = {
*/
url: string;
};
+ name?: string;
+ description?: string;
url: string;
port: number;
https?: { [x: string]: string };