diff options
Diffstat (limited to 'src/client/app/common')
10 files changed, 221 insertions, 67 deletions
diff --git a/src/client/app/common/scripts/date-stringify.ts b/src/client/app/common/scripts/date-stringify.ts deleted file mode 100644 index 2b8e525567..0000000000 --- a/src/client/app/common/scripts/date-stringify.ts +++ /dev/null @@ -1,13 +0,0 @@ -export default date => { - if (typeof date == 'string') date = new Date(date); - return ( - date.getFullYear() + '%i18n:common.date.full-year%' + - (date.getMonth() + 1) + '%i18n:common.date.month%' + - date.getDate() + '%i18n:common.date.day%' + - ' ' + - date.getHours() + '%i18n:common.date.hours%' + - date.getMinutes() + '%i18n:common.date.minutes%' + - ' ' + - `(${['日', '月', '火', '水', '木', '金', '土'][date.getDay()]})` - ); -}; diff --git a/src/client/app/common/views/components/autocomplete.vue b/src/client/app/common/views/components/autocomplete.vue index cd6066877c..b274eaa0a0 100644 --- a/src/client/app/common/views/components/autocomplete.vue +++ b/src/client/app/common/views/components/autocomplete.vue @@ -132,7 +132,7 @@ export default Vue.extend({ this.users = users; this.fetching = false; } else { - (this as any).api('users/search_by_username', { + (this as any).api('users/search', { query: this.q, limit: 30 }).then(users => { diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue index bbfec2c1cc..d1809d741f 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.game.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue @@ -1,14 +1,18 @@ <template> <div class="xqnhankfuuilcwvhgsopeqncafzsquya"> - <header><b>{{ blackUser | userName }}</b>(%i18n:common.reversi.black%) vs <b>{{ whiteUser | userName }}</b>(%i18n:common.reversi.white%)</header> + <button class="go-index" v-if="selfNav" @click="goIndex">%fa:arrow-left%</button> + <header><b><router-link :to="blackUser | userPage">{{ blackUser | userName }}</router-link></b>(%i18n:common.reversi.black%) vs <b><router-link :to="whiteUser | userPage">{{ whiteUser | userName }}</router-link></b>(%i18n:common.reversi.white%)</header> - <div style="overflow: hidden"> + <div style="overflow: hidden; line-height: 28px;"> <p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}<mk-ellipsis/></p> <p class="turn" v-if="logPos != logs.length">{{ '%i18n:common.reversi.past-turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}</p> <p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p> <p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p> <p class="result" v-if="game.isEnded && logPos == logs.length"> - <template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', $options.filters.userName(game.winner)) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template> + <template v-if="game.winner"> + <span>{{ '%i18n:common.reversi.won%'.replace('{}', $options.filters.userName(game.winner)) }}</span> + <span v-if="game.surrendered != null"> (%i18n:@surrendered%)</span> + </template> <template v-else>%i18n:common.reversi.drawn%</template> </p> </div> @@ -41,6 +45,10 @@ <p class="status"><b>{{ '%i18n:common.reversi.this-turn%'.split('{}')[0] }}{{ logPos }}{{ '%i18n:common.reversi.this-turn%'.split('{}')[1] }}</b> %i18n:common.reversi.black%:{{ o.blackCount }} %i18n:common.reversi.white%:{{ o.whiteCount }} %i18n:common.reversi.total%:{{ o.blackCount + o.whiteCount }}</p> + <div class="actions" v-if="!game.isEnded && iAmPlayer"> + <form-button @click="surrender">%i18n:@surrender%</form-button> + </div> + <div class="player" v-if="game.isEnded"> <el-button-group> <el-button type="primary" @click="logPos = 0" :disabled="logPos == 0">%fa:angle-double-left%</el-button> @@ -62,7 +70,20 @@ import Reversi, { Color } from '../../../../../../../games/reversi/core'; import { url } from '../../../../../config'; export default Vue.extend({ - props: ['initGame', 'connection'], + props: { + initGame: { + type: Object, + require: true + }, + connection: { + type: Object, + require: true + }, + selfNav: { + type: Boolean, + require: true + } + }, data() { return { @@ -79,22 +100,27 @@ export default Vue.extend({ if (!this.$store.getters.isSignedIn) return false; return this.game.user1Id == this.$store.state.i.id || this.game.user2Id == this.$store.state.i.id; }, + myColor(): Color { if (!this.iAmPlayer) return null; if (this.game.user1Id == this.$store.state.i.id && this.game.black == 1) return true; if (this.game.user2Id == this.$store.state.i.id && this.game.black == 2) return true; return false; }, + opColor(): Color { if (!this.iAmPlayer) return null; return this.myColor === true ? false : true; }, + blackUser(): any { return this.game.black == 1 ? this.game.user1 : this.game.user2; }, + whiteUser(): any { return this.game.black == 1 ? this.game.user2 : this.game.user1; }, + turnUser(): any { if (this.o.turn === true) { return this.game.black == 1 ? this.game.user1 : this.game.user2; @@ -104,11 +130,13 @@ export default Vue.extend({ return null; } }, + isMyTurn(): boolean { if (!this.iAmPlayer) return false; if (this.turnUser == null) return false; return this.turnUser.id == this.$store.state.i.id; }, + cellsStyle(): any { return { 'grid-template-rows': `repeat(${this.game.settings.map.length}, 1fr)`, @@ -165,11 +193,13 @@ export default Vue.extend({ mounted() { this.connection.on('set', this.onSet); this.connection.on('rescue', this.onRescue); + this.connection.on('ended', this.onEnded); }, beforeDestroy() { this.connection.off('set', this.onSet); this.connection.off('rescue', this.onRescue); + this.connection.off('ended', this.onEnded); clearInterval(this.pollingClock); }, @@ -215,6 +245,10 @@ export default Vue.extend({ } }, + onEnded(x) { + this.game = x.game; + }, + checkEnd() { this.game.isEnded = this.o.isEnded; if (this.game.isEnded) { @@ -250,6 +284,16 @@ export default Vue.extend({ this.checkEnd(); this.$forceUpdate(); + }, + + surrender() { + (this as any).api('games/reversi/games/surrender', { + gameId: this.game.id + }); + }, + + goIndex() { + this.$emit('go-index'); } } }); @@ -261,10 +305,21 @@ export default Vue.extend({ root(isDark) text-align center + > .go-index + position absolute + top 0 + left 0 + z-index 1 + width 42px + height 42px + > header padding 8px border-bottom dashed 1px isDark ? #4c5761 : #c4cdd4 + a + color inherit + > .board width calc(100% - 16px) max-width 500px @@ -381,6 +436,9 @@ root(isDark) margin 0 padding 16px 0 + > .actions + padding-bottom 16px + > .player padding-bottom 32px diff --git a/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue b/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue index 4969a9347e..1539c88de0 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.gameroom.vue @@ -1,7 +1,7 @@ <template> <div> <x-room v-if="!g.isStarted" :game="g" :connection="connection"/> - <x-game v-else :init-game="g" :connection="connection"/> + <x-game v-else :init-game="g" :connection="connection" :self-nav="selfNav" @go-index="goIndex"/> </div> </template> @@ -16,7 +16,16 @@ export default Vue.extend({ XGame, XRoom }, - props: ['game'], + props: { + game: { + type: Object, + required: true + }, + selfNav: { + type: Boolean, + require: true + } + }, data() { return { connection: null, @@ -36,6 +45,9 @@ export default Vue.extend({ onStarted(game) { Object.assign(this.g, game); this.$forceUpdate(); + }, + goIndex() { + this.$emit('go-index'); } } }); diff --git a/src/client/app/common/views/components/games/reversi/reversi.index.vue b/src/client/app/common/views/components/games/reversi/reversi.index.vue index 026159a0fd..d4d35f6a86 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.index.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.index.vue @@ -96,11 +96,7 @@ export default Vue.extend({ methods: { go(game) { - (this as any).api('games/reversi/games/show', { - gameId: game.id - }).then(game => { - this.$emit('go', game); - }); + this.$emit('go', game); }, match() { diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue index de5040f630..aed8718dd0 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.room.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue @@ -53,7 +53,7 @@ </div> </div> - <div class="card" v-if="form"> + <div class="card form" v-if="form"> <header> <span>%i18n:@settings-of-the-bot%</span> </header> @@ -65,7 +65,7 @@ :key="message.id"/> <template v-for="item in form"> - <mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch> + <mk-switch v-if="item.type == 'switch'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm(item)">{{ item.desc || '' }}</mk-switch> <div class="card" v-if="item.type == 'radio'" :key="item.id"> <header> @@ -73,7 +73,17 @@ </header> <div> - <el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio> + <form-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :value="r.value" @change="onChangeForm(item)">{{ r.label }}</form-radio> + </div> + </div> + + <div class="card" v-if="item.type == 'slider'" :key="item.id"> + <header> + <span>{{ item.label }}</span> + </header> + + <div> + <input type="range" :min="item.min" :max="item.max" :step="item.step || 1" v-model="item.value" @change="onChangeForm(item)"/> </div> </div> @@ -83,7 +93,7 @@ </header> <div> - <el-input v-model="item.value" @change="onChangeForm($event, item)"/> + <el-input v-model="item.value" @change="onChangeForm(item)"/> </div> </div> </template> @@ -93,7 +103,7 @@ <footer> <p class="status"> - <template v-if="isAccepted && isOpAccepted">%i18n:@this-gane-is-started-soon%<mk-ellipsis/></template> + <template v-if="isAccepted && isOpAccepted">%i18n:@this-game-is-started-soon%<mk-ellipsis/></template> <template v-if="isAccepted && !isOpAccepted">%i18n:@waiting-for-other%<mk-ellipsis/></template> <template v-if="!isAccepted && isOpAccepted">%i18n:@waiting-for-me%</template> <template v-if="!isAccepted && !isOpAccepted">%i18n:@waiting-for-both%<mk-ellipsis/></template> @@ -210,11 +220,11 @@ export default Vue.extend({ this.messages.unshift(x.message); }, - onChangeForm(v, item) { + onChangeForm(item) { this.connection.send({ type: 'update-form', id: item.id, - value: v + value: item.value }); }, @@ -274,6 +284,9 @@ root(isDark) color isDark ? #fff : #606266 cursor pointer transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) + -webkit-appearance none + -moz-appearance none + appearance none &:hover border-color isDark ? #a7aebd : #c0c4cc @@ -312,12 +325,20 @@ root(isDark) &[data-none] border-color transparent + &.form + > div + > .card + .card + margin-top 16px + + input[type='range'] + width 100% + .card max-width 400px border-radius 4px background isDark ? #282C37 : #fff color isDark ? #fff : #303133 - box-shadow 0 2px 12px 0 rgba(#000, 0.1) + box-shadow 0 2px 12px 0 rgba(#000, isDark ? 0.7 : 0.1) > header padding 18px 20px diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue index 94431845b0..223ec4597a 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.vue @@ -1,7 +1,7 @@ <template> <div class="vchtoekanapleubgzioubdtmlkribzfd"> <div v-if="game"> - <x-gameroom :game="game"/> + <x-gameroom :game="game" :self-nav="selfNav" @go-index="goIndex"/> </div> <div class="matching" v-else-if="matching"> <h1>{{ '%i18n:@matching.waiting-for%'.split('{}')[0] }}<b>{{ matching | userName }}</b>{{ '%i18n:@matching.waiting-for%'.split('{}')[1] }}<mk-ellipsis/></h1> @@ -9,6 +9,9 @@ <form-button round @click="cancel">%i18n:@matching.cancel%</form-button> </div> </div> + <div v-else-if="gameId"> + ... + </div> <div class="index" v-else> <x-index @go="nav" @matching="onMatching"/> </div> @@ -31,6 +34,11 @@ export default Vue.extend({ gameId: { type: String, required: false + }, + selfNav: { + type: Boolean, + require: false, + default: true } }, @@ -45,22 +53,18 @@ export default Vue.extend({ }, watch: { - gameId(id) { - if (id == null) { - this.game = null; - } else { - Progress.start(); - (this as any).api('games/reversi/games/show', { - gameId: id - }).then(game => { - this.nav(game, true); - Progress.done(); - }); - } + game() { + this.$emit('gamed', this.game); + }, + + gameId() { + this.fetch(); } }, mounted() { + this.fetch(); + if (this.$store.getters.isSignedIn) { this.connection = (this as any).os.streams.reversiStream.getConnection(); this.connectionId = (this as any).os.streams.reversiStream.use(); @@ -88,12 +92,32 @@ export default Vue.extend({ }, methods: { - nav(game, silent) { - this.matching = null; - this.game = game; + fetch() { + if (this.gameId == null) { + this.game = null; + } else { + Progress.start(); + (this as any).api('games/reversi/games/show', { + gameId: this.gameId + }).then(game => { + this.game = game; + Progress.done(); + }); + } + }, - if (!silent) { - this.$emit('nav', this.game); + async nav(game, actualNav = true) { + if (this.selfNav) { + // 受け取ったゲーム情報が省略されたものなら完全な情報を取得する + if (game != null && (game.settings == null || game.settings.map == null)) { + game = await (this as any).api('games/reversi/games/show', { + gameId: game.id + }); + } + + this.game = game; + } else { + this.$emit('nav', game, actualNav); } }, @@ -112,7 +136,8 @@ export default Vue.extend({ }).then(game => { if (game) { this.matching = null; - this.game = game; + + this.nav(game); } }); }, @@ -120,6 +145,11 @@ export default Vue.extend({ onMatched(game) { this.matching = null; this.game = game; + this.nav(game, false); + }, + + goIndex() { + this.nav(null); } } }); diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.ts b/src/client/app/common/views/components/misskey-flavored-markdown.ts index 1480c0325c..e97da4302c 100644 --- a/src/client/app/common/views/components/misskey-flavored-markdown.ts +++ b/src/client/app/common/views/components/misskey-flavored-markdown.ts @@ -1,5 +1,6 @@ import Vue from 'vue'; import * as emojilib from 'emojilib'; +import { length } from 'stringz'; import parse from '../../../../../mfm/parse'; import getAcct from '../../../../../misc/acct/render'; import { url } from '../../../config'; @@ -40,10 +41,13 @@ export default Vue.component('misskey-flavored-markdown', { ast = this.ast; } + let bigCount = 0; + let motionCount = 0; + // Parse ast to DOM const els = flatten(ast.map(token => { switch (token.type) { - case 'text': + case 'text': { const text = token.content.replace(/(\r\n|\n|\r)/g, '\n'); if (this.shouldBreak) { @@ -54,19 +58,52 @@ export default Vue.component('misskey-flavored-markdown', { } else { return createElement('span', text.replace(/\n/g, ' ')); } + } + + case 'bold': { + return createElement('b', token.bold); + } + + case 'big': { + bigCount++; + const isLong = length(token.big) > 10; + const isMany = bigCount > 3; + return (createElement as any)('strong', { + attrs: { + style: `display: inline-block; font-size: ${ isMany ? '100%' : '150%' };` + }, + directives: [this.$store.state.settings.disableAnimatedMfm || isLong || isMany ? {} : { + name: 'animate-css', + value: { classes: 'tada', iteration: 'infinite' } + }] + }, token.big); + } - case 'bold': - return createElement('strong', token.bold); + case 'motion': { + motionCount++; + const isLong = length(token.motion) > 10; + const isMany = motionCount > 3; + return (createElement as any)('span', { + attrs: { + style: 'display: inline-block;' + }, + directives: [this.$store.state.settings.disableAnimatedMfm || isLong || isMany ? {} : { + name: 'animate-css', + value: { classes: 'rubberBand', iteration: 'infinite' } + }] + }, token.motion); + } - case 'url': + case 'url': { return createElement(MkUrl, { props: { url: token.content, target: '_blank' } }); + } - case 'link': + case 'link': { return createElement('a', { attrs: { class: 'link', @@ -75,8 +112,9 @@ export default Vue.component('misskey-flavored-markdown', { title: token.url } }, token.title); + } - case 'mention': + case 'mention': { return (createElement as any)('a', { attrs: { href: `${url}/@${getAcct(token)}`, @@ -88,16 +126,18 @@ export default Vue.component('misskey-flavored-markdown', { value: token.content }] }, token.content); + } - case 'hashtag': + case 'hashtag': { return createElement('a', { attrs: { href: `${url}/tags/${encodeURIComponent(token.hashtag)}`, target: '_blank' } }, token.content); + } - case 'code': + case 'code': { return createElement('pre', { class: 'code' }, [ @@ -107,15 +147,17 @@ export default Vue.component('misskey-flavored-markdown', { } }) ]); + } - case 'inline-code': + case 'inline-code': { return createElement('code', { domProps: { innerHTML: token.html } }); + } - case 'quote': + case 'quote': { const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n'); if (this.shouldBreak) { @@ -134,27 +176,32 @@ export default Vue.component('misskey-flavored-markdown', { } }, text2.replace(/\n/g, ' ')); } + } - case 'title': + case 'title': { return createElement('div', { attrs: { class: 'title' } }, token.title); + } - case 'emoji': + case 'emoji': { const emoji = emojilib.lib[token.emoji]; return createElement('span', emoji ? emoji.char : token.content); + } - case 'search': + case 'search': { return createElement(MkGoogle, { props: { q: token.query } }); + } - default: + default: { console.log('unknown ast type:', token.type); + } } })); diff --git a/src/client/app/common/views/components/signin.vue b/src/client/app/common/views/components/signin.vue index 58241cef09..deaeeca6a7 100644 --- a/src/client/app/common/views/components/signin.vue +++ b/src/client/app/common/views/components/signin.vue @@ -12,7 +12,7 @@ </ui-input> <ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/> <ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button> - <p style="margin: 8px 0;">または<a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a></p> + <p style="margin: 8px 0;">%i18n:@or%<a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p> </form> </template> diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue index 6e1475bc38..9c37b3118b 100644 --- a/src/client/app/common/views/components/ui/form/button.vue +++ b/src/client/app/common/views/components/ui/form/button.vue @@ -45,6 +45,9 @@ root(isDark) color isDark ? #fff : #606266 transition 0.1s + * + pointer-events none + &:hover &:focus color $theme-color |