summaryrefslogtreecommitdiff
path: root/src/client/app/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/app/common')
-rw-r--r--src/client/app/common/scripts/date-stringify.ts13
-rw-r--r--src/client/app/common/views/components/autocomplete.vue2
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.game.vue66
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.gameroom.vue16
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.index.vue6
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.room.vue37
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.vue68
-rw-r--r--src/client/app/common/views/components/misskey-flavored-markdown.ts75
-rw-r--r--src/client/app/common/views/components/signin.vue2
-rw-r--r--src/client/app/common/views/components/ui/form/button.vue3
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