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/can-hide-text.ts16
-rw-r--r--src/client/app/common/scripts/check-for-update.ts5
-rw-r--r--src/client/app/common/scripts/compose-notification.ts62
-rw-r--r--src/client/app/common/scripts/get-kao.ts2
-rw-r--r--src/client/app/common/views/components/google.vue2
-rw-r--r--src/client/app/common/views/components/index.ts4
-rw-r--r--src/client/app/common/views/components/messaging-room.message.vue4
-rw-r--r--src/client/app/common/views/components/misskey-flavored-markdown.ts (renamed from src/client/app/common/views/components/note-html.ts)15
-rw-r--r--src/client/app/common/views/components/note-header.vue2
-rw-r--r--src/client/app/common/views/components/reaction-picker.vue35
-rw-r--r--src/client/app/common/views/components/reversi.game.vue162
-rw-r--r--src/client/app/common/views/components/reversi.vue2
-rw-r--r--src/client/app/common/views/components/welcome-timeline.vue10
-rw-r--r--src/client/app/common/views/pages/follow.vue215
-rw-r--r--src/client/app/common/views/widgets/hashtags.vue7
15 files changed, 425 insertions, 118 deletions
diff --git a/src/client/app/common/scripts/can-hide-text.ts b/src/client/app/common/scripts/can-hide-text.ts
deleted file mode 100644
index 4a4be8d9d0..0000000000
--- a/src/client/app/common/scripts/can-hide-text.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export default function(note) {
- if (note.text == null) return true;
-
- let txt = note.text;
-
- if (note.media) {
- note.media.forEach(file => {
- txt = txt.replace(file.url, '');
- if (file.src) txt = txt.replace(file.src, '');
- });
-
- if (txt == '') return true;
- }
-
- return false;
-}
diff --git a/src/client/app/common/scripts/check-for-update.ts b/src/client/app/common/scripts/check-for-update.ts
index b5ba6916d1..4445eefc39 100644
--- a/src/client/app/common/scripts/check-for-update.ts
+++ b/src/client/app/common/scripts/check-for-update.ts
@@ -23,7 +23,10 @@ export default async function(mios: MiOS, force = false, silent = false) {
}
if (!silent) {
- alert('%i18n:common.update-available%'.replace('{newer}', newer).replace('{current}', current));
+ mios.apis.dialog({
+ title: '%i18n:common.update-available-title%',
+ text: '%i18n:common.update-available%'.replace('{newer}', newer).replace('{current}', current)
+ });
}
return newer;
diff --git a/src/client/app/common/scripts/compose-notification.ts b/src/client/app/common/scripts/compose-notification.ts
index cc28f75998..2e58649ac2 100644
--- a/src/client/app/common/scripts/compose-notification.ts
+++ b/src/client/app/common/scripts/compose-notification.ts
@@ -20,34 +20,6 @@ export default function(type, data): Notification {
icon: data.url + '?thumbnail&size=64'
};
- case 'mention':
- return {
- title: `${getUserName(data.user)}さんから:`,
- body: getNoteSummary(data),
- icon: data.user.avatarUrl + '?thumbnail&size=64'
- };
-
- case 'reply':
- return {
- title: `${getUserName(data.user)}さんから返信:`,
- body: getNoteSummary(data),
- icon: data.user.avatarUrl + '?thumbnail&size=64'
- };
-
- case 'quote':
- return {
- title: `${getUserName(data.user)}さんが引用:`,
- body: getNoteSummary(data),
- icon: data.user.avatarUrl + '?thumbnail&size=64'
- };
-
- case 'reaction':
- return {
- title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
- body: getNoteSummary(data.note),
- icon: data.user.avatarUrl + '?thumbnail&size=64'
- };
-
case 'unread_messaging_message':
return {
title: `${getUserName(data.user)}さんからメッセージ:`,
@@ -62,6 +34,40 @@ export default function(type, data): Notification {
icon: data.parent.avatarUrl + '?thumbnail&size=64'
};
+ case 'notification':
+ switch (data.type) {
+ case 'mention':
+ return {
+ title: `${getUserName(data.user)}さんから:`,
+ body: getNoteSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'reply':
+ return {
+ title: `${getUserName(data.user)}さんから返信:`,
+ body: getNoteSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'quote':
+ return {
+ title: `${getUserName(data.user)}さんが引用:`,
+ body: getNoteSummary(data),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ case 'reaction':
+ return {
+ title: `${getUserName(data.user)}: ${getReactionEmoji(data.reaction)}:`,
+ body: getNoteSummary(data.note),
+ icon: data.user.avatarUrl + '?thumbnail&size=64'
+ };
+
+ default:
+ return null;
+ }
+
default:
return null;
}
diff --git a/src/client/app/common/scripts/get-kao.ts b/src/client/app/common/scripts/get-kao.ts
index 2168c5be88..d380187510 100644
--- a/src/client/app/common/scripts/get-kao.ts
+++ b/src/client/app/common/scripts/get-kao.ts
@@ -1,5 +1,5 @@
export default () => [
'(=^・・^=)',
- 'v(‘ω’)v',
+ 'v('ω')v',
'🐡( \'-\' 🐡 )フグパンチ!!!!'
][Math.floor(Math.random() * 3)];
diff --git a/src/client/app/common/views/components/google.vue b/src/client/app/common/views/components/google.vue
index 92817d3c1f..8272961ef2 100644
--- a/src/client/app/common/views/components/google.vue
+++ b/src/client/app/common/views/components/google.vue
@@ -1,7 +1,7 @@
<template>
<div class="mk-google">
<input type="search" v-model="query" :placeholder="q">
- <button @click="search">検索</button>
+ <button @click="search">%fa:search% %i18n:common.search%</button>
</div>
</template>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index 5b2fa084fb..e2cba2e53b 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -9,7 +9,7 @@ import forkit from './forkit.vue';
import acct from './acct.vue';
import avatar from './avatar.vue';
import nav from './nav.vue';
-import noteHtml from './note-html';
+import misskeyFlavoredMarkdown from './misskey-flavored-markdown';
import poll from './poll.vue';
import pollEditor from './poll-editor.vue';
import reactionIcon from './reaction-icon.vue';
@@ -47,7 +47,7 @@ Vue.component('mk-forkit', forkit);
Vue.component('mk-acct', acct);
Vue.component('mk-avatar', avatar);
Vue.component('mk-nav', nav);
-Vue.component('mk-note-html', noteHtml);
+Vue.component('misskey-flavored-markdown', misskeyFlavoredMarkdown);
Vue.component('mk-poll', poll);
Vue.component('mk-poll-editor', pollEditor);
Vue.component('mk-reaction-icon', reactionIcon);
diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue
index a77b5f3658..f33173da6f 100644
--- a/src/client/app/common/views/components/messaging-room.message.vue
+++ b/src/client/app/common/views/components/messaging-room.message.vue
@@ -8,7 +8,7 @@
<img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
</button>
<div class="content" v-if="!message.isDeleted">
- <mk-note-html class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
+ <misskey-flavored-markdown class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
<div class="file" v-if="message.file">
<a :href="message.file.url" target="_blank" :title="message.file.name">
<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/>
@@ -32,7 +32,7 @@
<script lang="ts">
import Vue from 'vue';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
export default Vue.extend({
props: {
diff --git a/src/client/app/common/views/components/note-html.ts b/src/client/app/common/views/components/misskey-flavored-markdown.ts
index 8fa5f380dd..c321c76104 100644
--- a/src/client/app/common/views/components/note-html.ts
+++ b/src/client/app/common/views/components/misskey-flavored-markdown.ts
@@ -1,6 +1,6 @@
import Vue from 'vue';
import * as emojilib from 'emojilib';
-import parse from '../../../../../text/parse';
+import parse from '../../../../../mfm/parse';
import getAcct from '../../../../../acct/render';
import { url } from '../../../config';
import MkUrl from './url.vue';
@@ -10,7 +10,7 @@ const flatten = list => list.reduce(
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
);
-export default Vue.component('mk-note-html', {
+export default Vue.component('misskey-flavored-markdown', {
props: {
text: {
type: String,
@@ -40,17 +40,6 @@ export default Vue.component('mk-note-html', {
ast = this.ast;
}
- if (ast.filter(x => x.type != 'hashtag').length == 0) {
- return;
- }
-
- while (ast[ast.length - 1] && (
- ast[ast.length - 1].type == 'hashtag' ||
- (ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == ' ') ||
- (ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == '\n'))) {
- ast.pop();
- }
-
// Parse ast to DOM
const els = flatten(ast.map(token => {
switch (token.type) {
diff --git a/src/client/app/common/views/components/note-header.vue b/src/client/app/common/views/components/note-header.vue
index 6e64a6a6d3..25a3339264 100644
--- a/src/client/app/common/views/components/note-header.vue
+++ b/src/client/app/common/views/components/note-header.vue
@@ -72,6 +72,7 @@ root(isDark)
> .is-admin
> .is-bot
> .is-cat
+ flex-shrink 0
align-self center
margin 0 .5em 0 0
padding 1px 6px
@@ -89,6 +90,7 @@ root(isDark)
overflow hidden
text-overflow ellipsis
color isDark ? #606984 : #ccc
+ flex-shrink 2147483647
> .info
margin-left auto
diff --git a/src/client/app/common/views/components/reaction-picker.vue b/src/client/app/common/views/components/reaction-picker.vue
index 0db6f66b37..ed7aedb58e 100644
--- a/src/client/app/common/views/components/reaction-picker.vue
+++ b/src/client/app/common/views/components/reaction-picker.vue
@@ -1,7 +1,7 @@
<template>
<div class="mk-reaction-picker">
<div class="backdrop" ref="backdrop" @click="close"></div>
- <div class="popover" :class="{ compact }" ref="popover">
+ <div class="popover" :class="{ compact, big }" ref="popover">
<p v-if="!compact">{{ title }}</p>
<div>
<button @click="react('like')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="1" title="%i18n:common.reactions.like%"><mk-reaction-icon reaction='like'/></button>
@@ -25,7 +25,28 @@ import * as anime from 'animejs';
const placeholder = '%i18n:@choose-reaction%';
export default Vue.extend({
- props: ['note', 'source', 'compact', 'cb'],
+ props: {
+ note: {
+ type: Object,
+ required: true
+ },
+ source: {
+ required: true
+ },
+ compact: {
+ type: Boolean,
+ required: false,
+ default: false
+ },
+ cb: {
+ required: false
+ },
+ big: {
+ type: Boolean,
+ required: false,
+ default: false
+ }
+ },
data() {
return {
title: placeholder
@@ -162,6 +183,16 @@ root(isDark)
border-right solid $balloon-size transparent
border-bottom solid $balloon-size $bgcolor
+ &.compact
+ > div
+ width 280px
+
+ > button
+ width 50px
+ height 50px
+ font-size 28px
+ border-radius 4px
+
> p
display block
margin 0
diff --git a/src/client/app/common/views/components/reversi.game.vue b/src/client/app/common/views/components/reversi.game.vue
index dc79c95bb8..a2a6fd0dc1 100644
--- a/src/client/app/common/views/components/reversi.game.vue
+++ b/src/client/app/common/views/components/reversi.game.vue
@@ -3,24 +3,39 @@
<header><b>{{ blackUser.name }}</b>(黒) vs <b>{{ whiteUser.name }}</b>(白)</header>
<div style="overflow: hidden">
- <p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ turnUser.name }}のターンです<mk-ellipsis/></p>
- <p class="turn" v-if="logPos != logs.length">{{ turnUser.name }}のターン</p>
- <p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">相手のターンです<mk-ellipsis/></p>
- <p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">あなたのターンです</p>
+ <p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', turnUser.name) }}<mk-ellipsis/></p>
+ <p class="turn" v-if="logPos != logs.length">{{ '%i18n:common.reversi.past-turn-of%'.replace('{}', turnUser.name) }}</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"><b>{{ game.winner.name }}</b>の勝ち{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
- <template v-else>引き分け</template>
+ <template v-else>%i18n:common.reversi.drawn%</template>
</p>
</div>
- <div class="board" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
- <div v-for="(stone, i) in o.board"
- :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
- @click="set(i)"
- :title="'[' + (o.transformPosToXy(i)[0] + 1) + ', ' + (o.transformPosToXy(i)[1] + 1) + '] (' + i + ')'"
- >
- <img v-if="stone === true" :src="`${blackUser.avatarUrl}?thumbnail&size=128`" alt="">
- <img v-if="stone === false" :src="`${whiteUser.avatarUrl}?thumbnail&size=128`" alt="">
+ <div class="board">
+ <div class="labels-x" v-if="this.$store.state.settings.reversiBoardLabels">
+ <span v-for="i in game.settings.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
+ </div>
+ <div class="flex">
+ <div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels">
+ <div v-for="i in game.settings.map.length">{{ i }}</div>
+ </div>
+ <div class="cells" :style="cellsStyle">
+ <div v-for="(stone, i) in o.board"
+ :class="{ empty: stone == null, none: o.map[i] == 'null', isEnded: game.isEnded, myTurn: !game.isEnded && isMyTurn, can: turnUser ? o.canPut(turnUser.id == blackUser.id, i) : null, prev: o.prevPos == i }"
+ @click="set(i)"
+ :title="`${String.fromCharCode(65 + o.transformPosToXy(i)[0])}${o.transformPosToXy(i)[1] + 1}`">
+ <img v-if="stone === true" :src="`${blackUser.avatarUrl}?thumbnail&size=128`" alt="">
+ <img v-if="stone === false" :src="`${whiteUser.avatarUrl}?thumbnail&size=128`" alt="">
+ </div>
+ </div>
+ <div class="labels-y" v-if="this.$store.state.settings.reversiBoardLabels">
+ <div v-for="i in game.settings.map.length">{{ i }}</div>
+ </div>
+ </div>
+ <div class="labels-x" v-if="this.$store.state.settings.reversiBoardLabels">
+ <span v-for="i in game.settings.map[0].length">{{ String.fromCharCode(64 + i) }}</span>
</div>
</div>
@@ -92,6 +107,12 @@ export default Vue.extend({
isMyTurn(): boolean {
if (this.turnUser == null) return null;
return this.turnUser.id == this.$store.state.i.id;
+ },
+ cellsStyle(): any {
+ return {
+ 'grid-template-rows': `repeat(${ this.game.settings.map.length }, 1fr)`,
+ 'grid-template-columns': `repeat(${ this.game.settings.map[0].length }, 1fr)`
+ };
}
},
@@ -244,54 +265,99 @@ export default Vue.extend({
border-bottom dashed 1px #c4cdd4
> .board
- display grid
- grid-gap 4px
- width 350px
- height 350px
+ width calc(100% - 16px)
+ max-width 500px
margin 0 auto
- > div
- background transparent
- border-radius 6px
- overflow hidden
+ $label-size = 16px
+ $gap = 4px
+
+ > .labels-x
+ height $label-size
+ padding 0 $label-size
+ display flex
+
+ > *
+ flex 1
+ display flex
+ align-items center
+ justify-content center
+ font-size 12px
+
+ &:first-child
+ margin-left -($gap / 2)
+
+ &:last-child
+ margin-right -($gap / 2)
+
+ > .flex
+ display flex
+
+ > .labels-y
+ width $label-size
+ display flex
+ flex-direction column
+
+ > *
+ flex 1
+ display flex
+ align-items center
+ justify-content center
+ font-size 12px
+
+ &:first-child
+ margin-top -($gap / 2)
+
+ &:last-child
+ margin-bottom -($gap / 2)
+
+ > .cells
+ flex 1
+ display grid
+ grid-gap $gap
+
+ > div
+ background transparent
+ border-radius 6px
+ overflow hidden
- *
- pointer-events none
- user-select none
+ *
+ pointer-events none
+ user-select none
- &.empty
- border solid 2px #eee
+ &.empty
+ border solid 2px #eee
- &.empty.can
- background #eee
+ &.empty.can
+ background #eee
- &.empty.myTurn
- border-color #ddd
+ &.empty.myTurn
+ border-color #ddd
- &.can
- background #eee
- cursor pointer
+ &.can
+ background #eee
+ cursor pointer
- &:hover
- border-color darken($theme-color, 10%)
- background $theme-color
+ &:hover
+ border-color darken($theme-color, 10%)
+ background $theme-color
- &:active
- background darken($theme-color, 10%)
+ &:active
+ background darken($theme-color, 10%)
- &.prev
- box-shadow 0 0 0 4px rgba($theme-color, 0.7)
+ &.prev
+ box-shadow 0 0 0 4px rgba($theme-color, 0.7)
- &.isEnded
- border-color #ddd
+ &.isEnded
+ border-color #ddd
- &.none
- border-color transparent !important
+ &.none
+ border-color transparent !important
- > img
- display block
- width 100%
- height 100%
+ > img
+ display block
+ width 100%
+ height 100%
> .graph
display grid
diff --git a/src/client/app/common/views/components/reversi.vue b/src/client/app/common/views/components/reversi.vue
index e4d7740bde..61705163ac 100644
--- a/src/client/app/common/views/components/reversi.vue
+++ b/src/client/app/common/views/components/reversi.vue
@@ -10,7 +10,7 @@
</div>
</div>
<div class="index" v-else>
- <h1>Misskey %fa:circle%thell%fa:circle R%</h1>
+ <h1>Misskey Reversi</h1>
<p>他のMisskeyユーザーとリバーシで対戦しよう</p>
<div class="play">
<el-button round>フリーマッチ(準備中)</el-button>
diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue
index f3372bf062..5a8b9df476 100644
--- a/src/client/app/common/views/components/welcome-timeline.vue
+++ b/src/client/app/common/views/components/welcome-timeline.vue
@@ -13,7 +13,7 @@
</div>
</header>
<div class="text">
- <mk-note-html v-if="note.text" :text="note.text"/>
+ <misskey-flavored-markdown v-if="note.text" :text="note.text"/>
</div>
</div>
</div>
@@ -24,6 +24,13 @@
import Vue from 'vue';
export default Vue.extend({
+ props: {
+ max: {
+ type: Number,
+ required: false,
+ default: undefined
+ }
+ },
data() {
return {
fetching: true,
@@ -37,6 +44,7 @@ export default Vue.extend({
fetch(cb?) {
this.fetching = true;
(this as any).api('notes', {
+ limit: this.max,
local: true,
reply: false,
renote: false,
diff --git a/src/client/app/common/views/pages/follow.vue b/src/client/app/common/views/pages/follow.vue
new file mode 100644
index 0000000000..c8e838be85
--- /dev/null
+++ b/src/client/app/common/views/pages/follow.vue
@@ -0,0 +1,215 @@
+<template>
+<div class="syxhndwprovvuqhmyvveewmbqayniwkv" v-if="!fetching" :data-darkmode="$store.state.device.darkmode">
+ <div class="signed-in-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + myName + '</b>')"></div>
+
+ <main>
+ <div class="banner" :style="bannerStyle"></div>
+ <mk-avatar class="avatar" :user="user" :disable-preview="true"/>
+ <div class="body">
+ <router-link :to="user | userPage" class="name">{{ user | userName }}</router-link>
+ <span class="username">@{{ user | acct }}</span>
+ <div class="description">
+ <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>
+ </div>
+ </div>
+ </main>
+
+ <button
+ :class="{ wait: followWait, active: user.isFollowing || user.hasPendingFollowRequestFromYou }"
+ @click="onClick"
+ :disabled="followWait">
+ <template v-if="!followWait">
+ <template v-if="user.hasPendingFollowRequestFromYou">%fa:hourglass-half% %i18n:@request-pending%</template>
+ <template v-else-if="user.isFollowing">%fa:minus% %i18n:@following%</template>
+ <template v-else-if="!user.isFollowing && user.isLocked">%fa:plus% %i18n:@follow-request%</template>
+ <template v-else-if="!user.isFollowing && !user.isLocked">%fa:plus% %i18n:@follow%</template>
+ </template>
+ <template v-else>%fa:spinner .pulse .fw%</template>
+ </button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import parseAcct from '../../../../../acct/parse';
+import getUserName from '../../../../../renderers/get-user-name';
+import Progress from '../../../common/scripts/loading';
+
+export default Vue.extend({
+ data() {
+ return {
+ fetching: true,
+ user: null,
+ followWait: false
+ };
+ },
+
+ computed: {
+ myName(): string {
+ return Vue.filter('userName')(this.$store.state.i);
+ },
+
+ bannerStyle(): any {
+ if (this.user.bannerUrl == null) return {};
+ return {
+ backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null,
+ backgroundImage: `url(${ this.user.bannerUrl })`
+ };
+ }
+ },
+
+ created() {
+ this.fetch();
+ },
+
+ methods: {
+ fetch() {
+ const acct = new URL(location.href).searchParams.get('acct');
+ this.fetching = true;
+ Progress.start();
+ (this as any).api('users/show', parseAcct(acct)).then(user => {
+ this.user = user;
+ this.fetching = false;
+ Progress.done();
+ document.title = getUserName(this.user) + ' | Misskey';
+ });
+ },
+
+ async onClick() {
+ this.followWait = true;
+
+ try {
+ if (this.user.isFollowing) {
+ this.user = await (this as any).api('following/delete', {
+ userId: this.user.id
+ });
+ } else {
+ if (this.user.isLocked && this.user.hasPendingFollowRequestFromYou) {
+ this.user = await (this as any).api('following/requests/cancel', {
+ userId: this.user.id
+ });
+ } else if (this.user.isLocked) {
+ this.user = await (this as any).api('following/create', {
+ userId: this.user.id
+ });
+ } else {
+ this.user = await (this as any).api('following/create', {
+ userId: this.user.id
+ });
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ } finally {
+ this.followWait = false;
+ }
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ padding 32px
+ max-width 500px
+ margin 0 auto
+ text-align center
+ color isDark ? #9baec8 : #868c8c
+
+ $bg = isDark ? #282C37 : #fff
+
+ @media (max-width 400px)
+ padding 16px
+
+ > .signed-in-as
+ margin-bottom 16px
+ font-size 14px
+ color isDark ? #9baec8 : #9daab3
+
+ > main
+ margin-bottom 16px
+ background $bg
+ border-radius 8px
+ box-shadow 0 4px 12px rgba(#000, 0.1)
+ overflow hidden
+
+ > .banner
+ height 128px
+ background-position center
+ background-size cover
+
+ > .avatar
+ display block
+ margin -50px auto 0 auto
+ width 100px
+ height 100px
+ border-radius 100%
+ border solid 4px $bg
+
+ > .body
+ padding 4px 32px 32px 32px
+
+ @media (max-width 400px)
+ padding 4px 16px 16px 16px
+
+ > .name
+ font-size 20px
+ font-weight bold
+
+ > .username
+ display block
+ opacity 0.7
+
+ > .description
+ margin-top 16px
+
+ > button
+ display block
+ user-select none
+ cursor pointer
+ padding 10px 16px
+ margin 0
+ width 100%
+ min-width 150px
+ font-size 14px
+ font-weight bold
+ color $theme-color
+ background transparent
+ outline none
+ border solid 1px $theme-color
+ border-radius 36px
+
+ &:hover
+ background rgba($theme-color, 0.1)
+
+ &:active
+ background rgba($theme-color, 0.2)
+
+ &.active
+ color $theme-color-foreground
+ background $theme-color
+
+ &:hover
+ background lighten($theme-color, 10%)
+ border-color lighten($theme-color, 10%)
+
+ &:active
+ background darken($theme-color, 10%)
+ border-color darken($theme-color, 10%)
+
+ &.wait
+ cursor wait !important
+ opacity 0.7
+
+ *
+ pointer-events none
+
+.syxhndwprovvuqhmyvveewmbqayniwkv[data-darkmode]
+ root(true)
+
+.syxhndwprovvuqhmyvveewmbqayniwkv:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/widgets/hashtags.vue b/src/client/app/common/views/widgets/hashtags.vue
index 9ab855d927..2065bd407c 100644
--- a/src/client/app/common/views/widgets/hashtags.vue
+++ b/src/client/app/common/views/widgets/hashtags.vue
@@ -6,7 +6,9 @@
<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
- <transition-group v-else tag="div" name="chart">
+ <!-- トランジションを有効にするとなぜかメモリリークする -->
+ <!-- <transition-group v-else tag="div" name="chart"> -->
+ <div>
<div v-for="stat in stats" :key="stat.tag">
<div class="tag">
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
@@ -14,7 +16,8 @@
</div>
<x-chart class="chart" :src="stat.chart"/>
</div>
- </transition-group>
+ </div>
+ <!-- </transition-group> -->
</div>
</mk-widget-container>
</div>