diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2018-06-27 00:55:44 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-06-27 00:55:44 +0900 |
| commit | 9ac34badad7a99166907e1a42f8a817413a74d58 (patch) | |
| tree | eb45b75f1f5f87a36f0eefec42f449fda109a6ad /src/client | |
| parent | New translations ja.yml (Portuguese) (diff) | |
| parent | Merge pull request #1789 from syuilo/greenkeeper/uuid-3.3.0 (diff) | |
| download | misskey-9ac34badad7a99166907e1a42f8a817413a74d58.tar.gz misskey-9ac34badad7a99166907e1a42f8a817413a74d58.tar.bz2 misskey-9ac34badad7a99166907e1a42f8a817413a74d58.zip | |
Merge branch 'master' into l10n_master
Diffstat (limited to 'src/client')
49 files changed, 1040 insertions, 660 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> diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts index 201ab0a83d..297100e0e0 100644 --- a/src/client/app/desktop/script.ts +++ b/src/client/app/desktop/script.ts @@ -36,6 +36,7 @@ import MkSearch from './views/pages/search.vue'; import MkTag from './views/pages/tag.vue'; import MkReversi from './views/pages/reversi.vue'; import MkShare from './views/pages/share.vue'; +import MkFollow from '../common/views/pages/follow.vue'; /** * init @@ -67,7 +68,8 @@ init(async (launch) => { { path: '/reversi', component: MkReversi }, { path: '/reversi/:game', component: MkReversi }, { path: '/@:user', component: MkUser }, - { path: '/notes/:note', component: MkNote } + { path: '/notes/:note', component: MkNote }, + { path: '/authorize-follow', component: MkFollow } ] }); @@ -115,26 +117,8 @@ function registerNotifications(stream: HomeStreamManager) { }); function attach(connection) { - connection.on('drive_file_created', file => { - const _n = composeNotification('drive_file_created', file); - const n = new Notification(_n.title, { - body: _n.body, - icon: _n.icon - }); - setTimeout(n.close.bind(n), 5000); - }); - - connection.on('mention', note => { - const _n = composeNotification('mention', note); - const n = new Notification(_n.title, { - body: _n.body, - icon: _n.icon - }); - setTimeout(n.close.bind(n), 6000); - }); - - connection.on('reply', note => { - const _n = composeNotification('reply', note); + connection.on('notification', notification => { + const _n = composeNotification('notification', notification); const n = new Notification(_n.title, { body: _n.body, icon: _n.icon @@ -142,13 +126,13 @@ function registerNotifications(stream: HomeStreamManager) { setTimeout(n.close.bind(n), 6000); }); - connection.on('quote', note => { - const _n = composeNotification('quote', note); + connection.on('drive_file_created', file => { + const _n = composeNotification('drive_file_created', file); const n = new Notification(_n.title, { body: _n.body, icon: _n.icon }); - setTimeout(n.close.bind(n), 6000); + setTimeout(n.close.bind(n), 5000); }); connection.on('unread_messaging_message', message => { diff --git a/src/client/app/desktop/views/components/home.vue b/src/client/app/desktop/views/components/home.vue index ba48ce24e8..ab276d3c4b 100644 --- a/src/client/app/desktop/views/components/home.vue +++ b/src/client/app/desktop/views/components/home.vue @@ -66,7 +66,7 @@ </div> <div class="main"> <mk-post-form class="form" v-if="$store.state.settings.showPostFormOnTopOfTl"/> - <mk-timeline class="tl" cref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/> + <mk-timeline class="tl" ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/> </div> </template> </div> diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue index 4b5e5bebdf..e3d16d7056 100644 --- a/src/client/app/desktop/views/components/note-detail.vue +++ b/src/client/app/desktop/views/components/note-detail.vue @@ -40,16 +40,13 @@ <div class="text"> <span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span> <span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span> - <mk-note-html v-if="p.text" :text="p.text" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media" :raw="true"/> </div> <mk-poll v-if="p.poll" :note="p"/> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> - <div class="tags" v-if="p.tags && p.tags.length > 0"> - <router-link v-for="tag in p.tags" :key="tag" :to="`/tags/${tag}`">{{ tag }}</router-link> - </div> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a> <div class="map" v-if="p.geo" ref="map"></div> <div class="renote" v-if="p.renote"> @@ -83,7 +80,7 @@ <script lang="ts"> import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; -import parse from '../../../../../text/parse'; +import parse from '../../../../../mfm/parse'; import MkPostFormWindow from './post-form-window.vue'; import MkRenoteFormWindow from './renote-form-window.vue'; @@ -363,35 +360,6 @@ root(isDark) > .mk-url-preview margin-top 8px - > .tags - margin 4px 0 0 0 - - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background #edf0f3 - border-radius 4px - - &:before - content "" - display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background #fff - border-radius 100% - - &:hover - text-decoration none - background #e2e7ec - > footer font-size 1.2em diff --git a/src/client/app/desktop/views/components/notes.note.vue b/src/client/app/desktop/views/components/notes.note.vue index ee11fcc55f..b8aff2d86e 100644 --- a/src/client/app/desktop/views/components/notes.note.vue +++ b/src/client/app/desktop/views/components/notes.note.vue @@ -25,16 +25,13 @@ <span v-if="p.isHidden" style="opacity: 0.5">%i18n:@private%</span> <span v-if="p.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span> <a class="reply" v-if="p.reply">%fa:reply%</a> - <mk-note-html v-if="p.text && !canHideText(p)" :text="p.text" :i="$store.state.i" :class="$style.text"/> + <misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/> <a class="rp" v-if="p.renote">RP:</a> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> - <div class="tags" v-if="p.tags && p.tags.length > 0"> - <router-link v-for="tag in p.tags" :key="tag" :to="`/tags/${tag}`">{{ tag }}</router-link> - </div> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% 位置情報</a> <div class="map" v-if="p.geo" ref="map"></div> <div class="renote" v-if="p.renote"> @@ -75,8 +72,7 @@ <script lang="ts"> import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; -import canHideText from '../../../common/scripts/can-hide-text'; -import parse from '../../../../../text/parse'; +import parse from '../../../../../mfm/parse'; import MkPostFormWindow from './post-form-window.vue'; import MkRenoteFormWindow from './renote-form-window.vue'; @@ -190,8 +186,6 @@ export default Vue.extend({ }, methods: { - canHideText, - capture(withHandler = false) { if (this.$store.getters.isSignedIn) { this.connection.send({ @@ -468,35 +462,6 @@ root(isDark) &:empty display none - > .tags - margin 4px 0 0 0 - - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background isDark ? #313543 : #edf0f3 - border-radius 4px - - &:before - content "" - display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background isDark ? #282c37 : #fff - border-radius 100% - - &:hover - text-decoration none - background #e2e7ec - .mk-url-preview margin-top 8px diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index 69f3739f79..1206eb7136 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -9,7 +9,9 @@ <button @click="resolveInitPromise">%i18n:@retry%</button> </div> - <transition-group name="mk-notes" class="transition"> + <!-- トランジションを有効にするとなぜかメモリリークする --> + <!--<transition-group name="mk-notes" class="transition">--> + <div class="notes"> <template v-for="(note, i) in _notes"> <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> @@ -17,7 +19,8 @@ <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> </p> </template> - </transition-group> + </div> + <!--</transition-group>--> <footer v-if="more"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> @@ -221,6 +224,7 @@ root(isDark) > * transition transform .3s ease, opacity .3s ease + > .notes > .date display block margin 0 diff --git a/src/client/app/desktop/views/components/notifications.vue b/src/client/app/desktop/views/components/notifications.vue index e479ffadbf..32b36994db 100644 --- a/src/client/app/desktop/views/components/notifications.vue +++ b/src/client/app/desktop/views/components/notifications.vue @@ -1,7 +1,9 @@ <template> <div class="mk-notifications"> <div class="notifications" v-if="notifications.length != 0"> - <transition-group name="mk-notifications" class="transition"> + <!-- トランジションを有効にするとなぜかメモリリークする --> + <!-- <transition-group name="mk-notifications" class="transition"> --> + <div> <template v-for="(notification, i) in _notifications"> <div class="notification" :class="notification.type" :key="notification.id"> <mk-time :time="notification.createdAt"/> @@ -95,7 +97,8 @@ <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span> </p> </template> - </transition-group> + </div> + <!-- </transition-group> --> </div> <button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }} @@ -201,7 +204,7 @@ root(isDark) transition transform .3s ease, opacity .3s ease > .notifications - > * + > div > .notification margin 0 padding 16px diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 33f2288e06..3832e5b38c 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -49,7 +49,7 @@ import Vue from 'vue'; import * as XDraggable from 'vuedraggable'; import getKao from '../../../common/scripts/get-kao'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; -import parse from '../../../../../text/parse'; +import parse from '../../../../../mfm/parse'; import { host } from '../../../config'; export default Vue.extend({ diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index 536d270dce..74ab45626d 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -54,6 +54,7 @@ <mk-switch v-model="$store.state.settings.showMaps" @change="onChangeShowMaps" text="%i18n:@show-maps%"> <span>%i18n:@show-maps-desc%</span> </mk-switch> + <mk-switch v-model="$store.state.settings.reversiBoardLabels" @change="onChangeReversiBoardLabels" text="%i18n:common.show-reversi-board-labels%"/> </section> <section class="web" v-show="page == 'web'"> @@ -369,6 +370,12 @@ export default Vue.extend({ value: v }); }, + onChangeReversiBoardLabels(v) { + this.$store.dispatch('settings/set', { + key: 'reversiBoardLabels', + value: v + }); + }, onChangeGradientWindowHeader(v) { this.$store.dispatch('settings/set', { key: 'gradientWindowHeader', diff --git a/src/client/app/desktop/views/components/sub-note-content.vue b/src/client/app/desktop/views/components/sub-note-content.vue index 45ce6a6f8f..cb0374b910 100644 --- a/src/client/app/desktop/views/components/sub-note-content.vue +++ b/src/client/app/desktop/views/components/sub-note-content.vue @@ -4,7 +4,7 @@ <span v-if="note.isHidden" style="opacity: 0.5">%i18n:@private%</span> <span v-if="note.deletedAt" style="opacity: 0.5">%i18n:@deleted%</span> <a class="reply" v-if="note.replyId">%fa:reply%</a> - <mk-note-html v-if="note.text" :text="note.text" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/> <a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RP: ...</a> </div> <details v-if="note.media.length > 0"> diff --git a/src/client/app/desktop/views/components/ui.header.vue b/src/client/app/desktop/views/components/ui.header.vue index 7045790054..aa7c3ac44d 100644 --- a/src/client/app/desktop/views/components/ui.header.vue +++ b/src/client/app/desktop/views/components/ui.header.vue @@ -4,7 +4,7 @@ <div class="main" ref="main"> <div class="backdrop"></div> <div class="main"> - <p ref="welcomeback" v-if="$store.getters.isSignedIn">おかえりなさい、<b>{{ $store.state.i | userName }}</b>さん</p> + <p ref="welcomeback" v-if="$store.getters.isSignedIn">%i18n:@welcome-back%<b>{{ $store.state.i | userName }}</b>さん</p> <div class="container" ref="mainContainer"> <div class="left"> <x-nav/> diff --git a/src/client/app/desktop/views/pages/deck/deck.note.vue b/src/client/app/desktop/views/pages/deck/deck.note.vue index 5a8dc2ea65..c7df715a05 100644 --- a/src/client/app/desktop/views/pages/deck/deck.note.vue +++ b/src/client/app/desktop/views/pages/deck/deck.note.vue @@ -25,16 +25,13 @@ <span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span> <span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span> <a class="reply" v-if="p.reply">%fa:reply%</a> - <mk-note-html v-if="p.text && !canHideText(p)" :text="p.text" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/> <a class="rp" v-if="p.renote != null">RP:</a> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> - <div class="tags" v-if="p.tags && p.tags.length > 0"> - <router-link v-for="tag in p.tags" :key="tag" :to="`/tags/${tag}`">{{ tag }}</router-link> - </div> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a> <div class="renote" v-if="p.renote"> <mk-note-preview :note="p.renote" :mini="true"/> @@ -67,8 +64,7 @@ <script lang="ts"> import Vue from 'vue'; -import parse from '../../../../../../text/parse'; -import canHideText from '../../../../common/scripts/can-hide-text'; +import parse from '../../../../../../mfm/parse'; import MkNoteMenu from '../../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../../common/views/components/reaction-picker.vue'; @@ -148,8 +144,6 @@ export default Vue.extend({ }, methods: { - canHideText, - capture(withHandler = false) { if (this.$store.getters.isSignedIn) { this.connection.send({ @@ -376,31 +370,6 @@ root(isDark) .mk-url-preview margin-top 8px - > .tags - margin 4px 0 0 0 - - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background isDark ? #313543 : #edf0f3 - border-radius 4px - - &:before - content "" - display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background isDark ? #282c37 : #fff - border-radius 100% - > .media > img display block diff --git a/src/client/app/desktop/views/pages/deck/deck.notes.vue b/src/client/app/desktop/views/pages/deck/deck.notes.vue index 8862b0e0fc..a5ed45b64c 100644 --- a/src/client/app/desktop/views/pages/deck/deck.notes.vue +++ b/src/client/app/desktop/views/pages/deck/deck.notes.vue @@ -7,7 +7,9 @@ <button @click="resolveInitPromise">%i18n:@retry%</button> </div> - <transition-group name="mk-notes" class="transition"> + <!-- トランジションを有効にするとなぜかメモリリークする --> + <!--<transition-group name="mk-notes" class="transition">--> + <div class="notes"> <template v-for="(note, i) in _notes"> <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :media-view="mediaView"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> @@ -15,7 +17,8 @@ <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> </p> </template> - </transition-group> + </div> + <!--</transition-group>--> <footer v-if="more"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> @@ -198,6 +201,7 @@ root(isDark) > * transition transform .3s ease, opacity .3s ease + > .notes > .date display block margin 0 diff --git a/src/client/app/desktop/views/pages/deck/deck.notifications.vue b/src/client/app/desktop/views/pages/deck/deck.notifications.vue index f54ad1a3cd..10c06b0ad2 100644 --- a/src/client/app/desktop/views/pages/deck/deck.notifications.vue +++ b/src/client/app/desktop/views/pages/deck/deck.notifications.vue @@ -1,6 +1,8 @@ <template> <div class="oxynyeqmfvracxnglgulyqfgqxnxmehl"> - <transition-group name="mk-notifications" class="transition notifications"> + <!-- トランジションを有効にするとなぜかメモリリークする --> + <!--<transition-group name="mk-notifications" class="transition notifications">--> + <div class="notifications"> <template v-for="(notification, i) in _notifications"> <x-notification class="notification" :notification="notification" :key="notification.id"/> <p class="date" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date" :key="notification.id + '-time'"> @@ -8,7 +10,8 @@ <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span> </p> </template> - </transition-group> + </div> + <!--</transition-group>--> <button class="more" :class="{ fetching: fetchingMoreNotifications }" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template>{{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:@more%' }} </button> diff --git a/src/client/app/desktop/views/pages/user/user.header.vue b/src/client/app/desktop/views/pages/user/user.header.vue index d52c6b762c..00545723e2 100644 --- a/src/client/app/desktop/views/pages/user/user.header.vue +++ b/src/client/app/desktop/views/pages/user/user.header.vue @@ -1,27 +1,37 @@ <template> <div class="header" :data-is-dark-background="user.bannerUrl != null"> - <div class="is-suspended" v-if="user.isSuspended"><p>%fa:exclamation-triangle% %i18n:@is-suspended%</p></div> - <div class="is-remote" v-if="user.host != null"><p>%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></p></div> <div class="banner-container" :style="style"> <div class="banner" ref="banner" :style="style" @click="onBannerClick"></div> <div class="fade"></div> - </div> - <div class="container"> - <mk-avatar class="avatar" :user="user" :disable-preview="true"/> <div class="title"> <p class="name">{{ user | userName }}</p> - <p class="username"><mk-acct :user="user"/></p> - <p class="location" v-if="user.host === null && user.profile.location">%fa:map-marker%{{ user.profile.location }}</p> + <div> + <span class="username"><mk-acct :user="user"/></span> + <span v-if="user.isBot" title="%i18n:@is-bot%">%fa:robot%</span> + <span class="location" v-if="user.host === null && user.profile.location">%fa:map-marker% {{ user.profile.location }}</span> + <span class="birthday" v-if="user.host === null && user.profile.birthday">%fa:birthday-cake% {{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</span> + </div> + </div> + </div> + <mk-avatar class="avatar" :user="user" :disable-preview="true"/> + <div class="body"> + <div class="description"> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + </div> + <div class="status"> + <span class="notes-count"><b>{{ user.notesCount | number }}</b>%i18n:@posts%</span> + <span class="following clickable" @click="showFollowing"><b>{{ user.followingCount | number }}</b>%i18n:@following%</span> + <span class="followers clickable" @click="showFollowers"><b>{{ user.followersCount | number }}</b>%i18n:@followers%</span> </div> - <footer> - <router-link :to="user | userPage" :data-active="$parent.page == 'home'">%fa:home%ホーム</router-link> - </footer> </div> </div> </template> <script lang="ts"> import Vue from 'vue'; +import MkFollowingWindow from '../../components/following-window.vue'; +import MkFollowersWindow from '../../components/followers-window.vue'; +import * as age from 's-age'; export default Vue.extend({ props: ['user'], @@ -32,20 +42,24 @@ export default Vue.extend({ backgroundColor: this.user.bannerColor && this.user.bannerColor.length == 3 ? `rgb(${ this.user.bannerColor.join(',') })` : null, backgroundImage: `url(${ this.user.bannerUrl })` }; + }, + + age(): number { + return age(this.user.profile.birthday); } }, mounted() { if (this.user.bannerUrl) { - window.addEventListener('load', this.onScroll); - window.addEventListener('scroll', this.onScroll, { passive: true }); - window.addEventListener('resize', this.onScroll); + //window.addEventListener('load', this.onScroll); + //window.addEventListener('scroll', this.onScroll, { passive: true }); + //window.addEventListener('resize', this.onScroll); } }, beforeDestroy() { if (this.user.bannerUrl) { - window.removeEventListener('load', this.onScroll); - window.removeEventListener('scroll', this.onScroll); - window.removeEventListener('resize', this.onScroll); + //window.removeEventListener('load', this.onScroll); + //window.removeEventListener('scroll', this.onScroll); + //window.removeEventListener('resize', this.onScroll); } }, methods: { @@ -68,7 +82,19 @@ export default Vue.extend({ (this as any).apis.updateBanner().then(i => { this.user.bannerUrl = i.bannerUrl; }); - } + }, + + showFollowing() { + (this as any).os.new(MkFollowingWindow, { + user: this.user + }); + }, + + showFollowers() { + (this as any).os.new(MkFollowersWindow, { + user: this.user + }); + }, } }); </script> @@ -76,31 +102,11 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.header - $footer-height = 58px - +root(isDark) + background isDark ? #282C37 : #fff + border 1px solid rgba(#000, 0.075) + border-radius 6px overflow hidden - background #f7f7f7 - box-shadow 0 1px 1px rgba(#000, 0.075) - - > .is-suspended - > .is-remote - &.is-suspended - color #570808 - background #ffdbdb - - &.is-remote - color #573c08 - background #fff0db - - > p - margin 0 auto - padding 14px 16px - max-width 1200px - font-size 14px - - > a - font-weight bold &[data-is-dark-background] > .banner-container @@ -110,7 +116,6 @@ export default Vue.extend({ > .fade background linear-gradient(transparent, rgba(#000, 0.7)) - > .container > .title color #fff @@ -118,7 +123,7 @@ export default Vue.extend({ text-shadow 0 0 8px #000 > .banner-container - height 320px + height 250px overflow hidden background-size cover background-position center @@ -136,83 +141,75 @@ export default Vue.extend({ width 100% height 78px - > .container - max-width 1200px - margin 0 auto - - > .avatar - display block - position absolute - bottom 16px - left 16px - z-index 2 - width 160px - height 160px - border solid 3px #fff - border-radius 8px - box-shadow 1px 1px 3px rgba(#000, 0.2) - > .title position absolute - bottom $footer-height + bottom 0 left 0 width 100% - padding 0 0 8px 195px + padding 0 0 8px 154px color #5e6367 - font-family '游ゴシック', 'YuGothic', 'ヒラギノ角ゴ ProN W3', 'Hiragino Kaku Gothic ProN', 'Meiryo', 'メイリオ', sans-serif > .name display block margin 0 - line-height 40px + line-height 32px font-weight bold - font-size 2em + font-size 1.8em - > .username - > .location - display inline-block - margin 0 16px 0 0 - line-height 20px - opacity 0.8 + > div + > * + display inline-block + margin-right 16px + line-height 20px + opacity 0.8 - > i - margin-right 4px + &.username + font-weight bold + + > .avatar + display block + position absolute + top 170px + left 16px + z-index 2 + width 120px + height 120px + box-shadow 1px 1px 3px rgba(#000, 0.2) - > footer - z-index 1 - height $footer-height - padding-left 195px + > .body + padding 16px 16px 16px 154px + color isDark ? #c5ced6 : #555 - > a + > .status + margin-top 16px + padding-top 16px + border-top solid 1px rgba(#000, isDark ? 0.2 : 0.1) + font-size 80% + + > * display inline-block - margin 0 - padding 0 16px - height $footer-height - line-height $footer-height - color #555 + padding-right 16px + margin-right 16px - &[data-active] - border-bottom solid 4px $theme-color + &:not(:last-child) + border-right solid 1px rgba(#000, isDark ? 0.2 : 0.1) - > i - margin-right 6px + &.clickable + cursor pointer - > button - display block - position absolute - top 0 - right 0 - margin 8px - padding 0 - width $footer-height - 16px - line-height $footer-height - 16px - 2px - font-size 1.2em - color #777 - border solid 1px #eee - border-radius 4px + &:hover + color isDark ? #fff : #000 + + > b + margin-right 4px + font-size 1rem + font-weight bold + color $theme-color + +.header[data-darkmode] + root(true) - &:hover - color #555 - border solid 1px #ddd +.header:not([data-darkmode]) + root(false) </style> diff --git a/src/client/app/desktop/views/pages/user/user.home.vue b/src/client/app/desktop/views/pages/user/user.home.vue deleted file mode 100644 index afaf97dc9e..0000000000 --- a/src/client/app/desktop/views/pages/user/user.home.vue +++ /dev/null @@ -1,103 +0,0 @@ -<template> -<div class="home"> - <div> - <div ref="left"> - <x-profile :user="user"/> - <x-photos :user="user"/> - <x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> - <p v-if="user.host === null">%i18n:@last-used-at%: <b><mk-time :time="user.lastUsedAt"/></b></p> - </div> - </div> - <main> - <mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/> - <x-timeline class="timeline" ref="tl" :user="user"/> - </main> - <div> - <div ref="right"> - <mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/> - <mk-activity :user="user"/> - <x-friends :user="user"/> - <div class="nav"><mk-nav/></div> - </div> - </div> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import XTimeline from './user.timeline.vue'; -import XProfile from './user.profile.vue'; -import XPhotos from './user.photos.vue'; -import XFollowersYouKnow from './user.followers-you-know.vue'; -import XFriends from './user.friends.vue'; - -export default Vue.extend({ - components: { - XTimeline, - XProfile, - XPhotos, - XFollowersYouKnow, - XFriends - }, - props: ['user'], - methods: { - warp(date) { - (this.$refs.tl as any).warp(date); - } - } -}); -</script> - -<style lang="stylus" scoped> -.home - display flex - justify-content center - margin 0 auto - max-width 1200px - - > main - > div > div - > *:not(:last-child) - margin-bottom 16px - - > main - padding 16px - width calc(100% - 275px * 2) - - > .timeline - border solid 1px rgba(#000, 0.075) - border-radius 6px - - > div - width 275px - margin 0 - - &:first-child > div - padding 16px 0 16px 16px - - > p - display block - margin 0 - padding 0 12px - text-align center - font-size 0.8em - color #aaa - - &:last-child > div - padding 16px 16px 16px 0 - - > .nav - padding 16px - font-size 12px - color #aaa - background #fff - border solid 1px rgba(#000, 0.075) - border-radius 6px - - a - color #999 - - i - color #ccc - -</style> diff --git a/src/client/app/desktop/views/pages/user/user.profile.vue b/src/client/app/desktop/views/pages/user/user.profile.vue index 5aa08f7c85..0134d6f0be 100644 --- a/src/client/app/desktop/views/pages/user/user.profile.vue +++ b/src/client/app/desktop/views/pages/user/user.profile.vue @@ -15,48 +15,17 @@ </button> <button class="mute ui" @click="list">%fa:list% リストに追加</button> </div> - <div class="description" v-if="user.description">{{ user.description }}</div> - <div class="birthday" v-if="user.host === null && user.profile.birthday"> - <p>%fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)</p> - </div> - <div class="twitter" v-if="user.host === null && user.twitter"> - <p>%fa:B twitter%<a :href="`https://twitter.com/${user.twitter.screenName}`" target="_blank">@{{ user.twitter.screenName }}</a></p> - </div> - <div class="status"> - <p class="notes-count">%fa:angle-right%<a>{{ user.notesCount }}</a><b>投稿</b></p> - <p class="following">%fa:angle-right%<a @click="showFollowing">{{ user.followingCount }}</a>人を<b>フォロー</b></p> - <p class="followers">%fa:angle-right%<a @click="showFollowers">{{ user.followersCount }}</a>人の<b>フォロワー</b></p> - </div> </div> </template> <script lang="ts"> import Vue from 'vue'; -import * as age from 's-age'; -import MkFollowingWindow from '../../components/following-window.vue'; -import MkFollowersWindow from '../../components/followers-window.vue'; import MkUserListsWindow from '../../components/user-lists-window.vue'; export default Vue.extend({ props: ['user'], - computed: { - age(): number { - return age(this.user.profile.birthday); - } - }, - methods: { - showFollowing() { - (this as any).os.new(MkFollowingWindow, { - user: this.user - }); - }, - - showFollowers() { - (this as any).os.new(MkFollowersWindow, { - user: this.user - }); - }, + methods: { stalk() { (this as any).api('following/stalk', { userId: this.user.id @@ -116,8 +85,8 @@ export default Vue.extend({ </script> <style lang="stylus" scoped> -.profile - background #fff +root(isDark) + background isDark ? #282C37 : #fff border solid 1px rgba(#000, 0.075) border-radius 6px @@ -127,7 +96,7 @@ export default Vue.extend({ > .friend-form padding 16px text-align center - border-top solid 1px #eee + border-bottom solid 1px isDark ? #21242f : #eee > .followed margin 12px 0 0 0 @@ -145,7 +114,7 @@ export default Vue.extend({ > .action-form padding 16px text-align center - border-top solid 1px #eee + border-bottom solid 1px isDark ? #21242f : #eee > * width 100% @@ -153,43 +122,10 @@ export default Vue.extend({ &:not(:last-child) margin-bottom 12px - > .description - padding 16px - color #555 - border-top solid 1px #eee - - > .birthday - padding 16px - color #555 - border-top solid 1px #eee - - > p - margin 0 - - > i - margin-right 8px - - > .twitter - padding 16px - color #555 - border-top solid 1px #eee - - > p - margin 0 - - > i - margin-right 8px - - > .status - padding 16px - color #555 - border-top solid 1px #eee - - > p - margin 8px 0 +.profile[data-darkmode] + root(true) - > i - margin-left 8px - margin-right 8px +.profile:not([data-darkmode]) + root(false) </style> diff --git a/src/client/app/desktop/views/pages/user/user.timeline.vue b/src/client/app/desktop/views/pages/user/user.timeline.vue index 812b5b4229..67987fcb94 100644 --- a/src/client/app/desktop/views/pages/user/user.timeline.vue +++ b/src/client/app/desktop/views/pages/user/user.timeline.vue @@ -1,9 +1,9 @@ <template> -<div class="timeline"> +<div class="oh5y2r7l5lx8j6jj791ykeiwgihheguk"> <header> - <span :data-active="mode == 'default'" @click="mode = 'default'">%i18n:@default%</span> - <span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'">%i18n:@with-replies%</span> - <span :data-active="mode == 'with-media'" @click="mode = 'with-media'">%i18n:@with-media%</span> + <span :data-active="mode == 'default'" @click="mode = 'default'">%fa:comment-alt R% %i18n:@default%</span> + <span :data-active="mode == 'with-replies'" @click="mode = 'with-replies'">%fa:comments% %i18n:@with-replies%</span> + <span :data-active="mode == 'with-media'" @click="mode = 'with-media'">%fa:images% %i18n:@with-media%</span> </header> <div class="loading" v-if="fetching"> <mk-ellipsis-icon/> @@ -114,25 +114,44 @@ export default Vue.extend({ <style lang="stylus" scoped> @import '~const.styl' -.timeline - background #fff +root(isDark) + background isDark ? #282C37 : #fff > header - padding 8px 16px - border-bottom solid 1px #eee + padding 0 8px + z-index 10 + background isDark ? #313543 : #fff + border-radius 6px 6px 0 0 + box-shadow 0 1px isDark ? rgba(#000, 0.15) : rgba(#000, 0.08) > span - margin-right 16px - line-height 27px - font-size 18px - color #555 + display inline-block + padding 0 10px + line-height 42px + font-size 12px + user-select none - &:not([data-active]) + &[data-active] color $theme-color + cursor default + font-weight bold + + &:before + content "" + display block + position absolute + bottom 0 + left -8px + width calc(100% + 16px) + height 2px + background $theme-color + + &:not([data-active]) + color isDark ? #9aa2a7 : #6f7477 cursor pointer &:hover - text-decoration underline + color isDark ? #d9dcde : #525a5f > .loading padding 64px 0 @@ -151,4 +170,10 @@ export default Vue.extend({ font-size 3em color #ccc +.oh5y2r7l5lx8j6jj791ykeiwgihheguk[data-darkmode] + root(true) + +.oh5y2r7l5lx8j6jj791ykeiwgihheguk:not([data-darkmode]) + root(false) + </style> diff --git a/src/client/app/desktop/views/pages/user/user.twitter.vue b/src/client/app/desktop/views/pages/user/user.twitter.vue new file mode 100644 index 0000000000..228ce1de96 --- /dev/null +++ b/src/client/app/desktop/views/pages/user/user.twitter.vue @@ -0,0 +1,26 @@ +<template> +<div class="adsvaidqfznoartcbplullnejvxjphcn"> + <span>%fa:B twitter%<a :href="`https://twitter.com/${user.twitter.screenName}`" target="_blank">@{{ user.twitter.screenName }}</a></span> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + props: ['user'] +}); +</script> + +<style lang="stylus" scoped> +.adsvaidqfznoartcbplullnejvxjphcn + padding 32px + background #1a94f2 + border-radius 6px + color #fff + + a + margin-left 8px + color #fff + +</style> diff --git a/src/client/app/desktop/views/pages/user/user.vue b/src/client/app/desktop/views/pages/user/user.vue index 3644286fbc..fc5c900037 100644 --- a/src/client/app/desktop/views/pages/user/user.vue +++ b/src/client/app/desktop/views/pages/user/user.vue @@ -1,8 +1,26 @@ <template> <mk-ui> - <div class="user" v-if="!fetching"> - <x-header :user="user"/> - <x-home v-if="page == 'home'" :user="user"/> + <div class="xygkxeaeontfaokvqmiblezmhvhostak" v-if="!fetching" :data-darkmode="$store.state.device.darkmode"> + <div class="is-suspended" v-if="user.isSuspended">%fa:exclamation-triangle% %i18n:@is-suspended%</div> + <div class="is-remote" v-if="user.host != null">%fa:exclamation-triangle% %i18n:@is-remote%<a :href="user.url || user.uri" target="_blank">%i18n:@view-remote%</a></div> + <main> + <div class="main"> + <x-header :user="user"/> + <mk-note-detail v-if="user.pinnedNote" :note="user.pinnedNote" :compact="true"/> + <x-timeline class="timeline" ref="tl" :user="user"/> + </div> + <div class="side"> + <x-profile :user="user"/> + <x-twitter :user="user" v-if="user.host === null && user.twitter"/> + <mk-calendar @chosen="warp" :start="new Date(user.createdAt)"/> + <mk-activity :user="user"/> + <x-photos :user="user"/> + <x-friends :user="user"/> + <x-followers-you-know v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/> + <div class="nav"><mk-nav/></div> + <p v-if="user.host === null">%i18n:@last-used-at%: <b><mk-time :time="user.lastUsedAt"/></b></p> + </div> + </main> </div> </mk-ui> </template> @@ -13,17 +31,22 @@ import parseAcct from '../../../../../../acct/parse'; import getUserName from '../../../../../../renderers/get-user-name'; import Progress from '../../../../common/scripts/loading'; import XHeader from './user.header.vue'; -import XHome from './user.home.vue'; +import XTimeline from './user.timeline.vue'; +import XProfile from './user.profile.vue'; +import XPhotos from './user.photos.vue'; +import XFollowersYouKnow from './user.followers-you-know.vue'; +import XFriends from './user.friends.vue'; +import XTwitter from './user.twitter.vue'; export default Vue.extend({ components: { XHeader, - XHome - }, - props: { - page: { - default: 'home' - } + XTimeline, + XProfile, + XPhotos, + XFollowersYouKnow, + XFriends, + XTwitter }, data() { return { @@ -47,8 +70,89 @@ export default Vue.extend({ Progress.done(); document.title = getUserName(this.user) + ' | Misskey'; }); + }, + + warp(date) { + (this.$refs.tl as any).warp(date); } } }); </script> +<style lang="stylus" scoped> +root(isDark) + width 980px + padding 16px + margin 0 auto + + > .is-suspended + > .is-remote + margin-bottom 16px + padding 14px 16px + font-size 14px + border-radius 6px + + &.is-suspended + color isDark ? #ffb4b4 : #570808 + background isDark ? #611d1d : #ffdbdb + border solid 1px isDark ? #d64a4a : #e09696 + + &.is-remote + color isDark ? #ffbd3e : #573c08 + background isDark ? #42321c : #fff0db + border solid 1px isDark ? #90733c : #dcbb7b + + > a + font-weight bold + + > main + display flex + justify-content center + + > .main + > .side + > *:not(:last-child) + margin-bottom 16px + + > .main + flex 1 + min-width 0 // SEE: http://kudakurage.hatenadiary.com/entry/2016/04/01/232722 + margin-right 16px + + > .timeline + border 1px solid rgba(#000, 0.075) + border-radius 6px + + > .side + width 275px + flex-shrink 0 + + > p + display block + margin 0 + padding 0 12px + text-align center + font-size 0.8em + color #aaa + + > .nav + padding 16px + font-size 12px + color #aaa + background #fff + border solid 1px rgba(#000, 0.075) + border-radius 6px + + a + color #999 + + i + color #ccc + +.xygkxeaeontfaokvqmiblezmhvhostak[data-darkmode] + root(true) + +.xygkxeaeontfaokvqmiblezmhvhostak:not([data-darkmode]) + root(false) + +</style> diff --git a/src/client/app/desktop/views/pages/welcome.vue b/src/client/app/desktop/views/pages/welcome.vue index 029e44e27c..cac4007b40 100644 --- a/src/client/app/desktop/views/pages/welcome.vue +++ b/src/client/app/desktop/views/pages/welcome.vue @@ -7,6 +7,13 @@ </button> <div class="body" :style="{ backgroundImage: `url('${ welcomeBgUrl }')` }"> <div class="container"> + <div class="info"> + <span>%i18n:common.misskey% <b>{{ host }}</b></span> + <span class="stats" v-if="stats"> + <span>%fa:user% {{ stats.originalUsersCount | number }}</span> + <span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span> + </span> + </div> <main> <div class="about"> <h1 v-if="name">{{ name }}</h1> @@ -19,12 +26,8 @@ <mk-signin/> </div> </main> - <div class="info"> - <span>%i18n:common.misskey% <b>{{ host }}</b></span> - <span class="stats" v-if="stats"> - <span>%fa:user% {{ stats.originalUsersCount | number }}</span> - <span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span> - </span> + <div class="hashtags"> + <router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link> </div> <mk-nav class="nav"/> </div> @@ -32,7 +35,7 @@ <img src="assets/title.dark.svg" alt="Misskey"> </div> <div class="tl"> - <mk-welcome-timeline/> + <mk-welcome-timeline :max="20"/> </div> <modal name="signup" width="500px" height="auto" scrollable> <header :class="$style.signupFormHeader">%i18n:@signup%</header> @@ -54,13 +57,18 @@ export default Vue.extend({ host, name, description, - pointerInterval: null + pointerInterval: null, + tags: [] }; }, created() { (this as any).api('stats').then(stats => { this.stats = stats; }); + + (this as any).api('hashtags/trend').then(stats => { + this.tags = stats.map(x => x.tag); + }); }, mounted() { this.point(); @@ -161,6 +169,20 @@ root(isDark) $loginWidth = 340px $width = $aboutWidth + $loginWidth + > .info + margin 0 auto 16px auto + width $width + font-size 14px + color #fff + + > .stats + margin-left 16px + padding-left 16px + border-left solid 1px #fff + + > * + margin-right 16px + > main display flex margin auto @@ -199,24 +221,19 @@ root(isDark) > .login width $loginWidth padding 16px 32px 32px 32px - background #f5f5f5 + background isDark ? #2e3440 : #f5f5f5 - > .info + > .hashtags margin 16px auto - padding 12px width $width font-size 14px color #fff - background rgba(#000, 0.2) + background rgba(#000, 0.3) border-radius 8px - > .stats - margin-left 16px - padding-left 16px - border-left solid 1px #fff - - > * - margin-right 16px + > * + display inline-block + margin 14px > .nav display block diff --git a/src/client/app/mobile/api/dialog.ts b/src/client/app/mobile/api/dialog.ts index a2378767be..23f35b7aa9 100644 --- a/src/client/app/mobile/api/dialog.ts +++ b/src/client/app/mobile/api/dialog.ts @@ -1,5 +1,18 @@ -export default function(opts) { +import OS from '../../mios'; +import Dialog from '../views/components/dialog.vue'; + +export default (os: OS) => opts => { return new Promise<string>((res, rej) => { - alert('dialog not implemented yet'); + const o = opts || {}; + const d = os.new(Dialog, { + title: o.title, + text: o.text, + modal: o.modal, + buttons: o.actions + }); + d.$once('clicked', id => { + res(id); + }); + document.body.appendChild(d.$el); }); -} +}; diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts index cc0a8331ba..cb43f9d520 100644 --- a/src/client/app/mobile/script.ts +++ b/src/client/app/mobile/script.ts @@ -38,6 +38,7 @@ import MkSettings from './views/pages/settings.vue'; import MkReversi from './views/pages/reversi.vue'; import MkTag from './views/pages/tag.vue'; import MkShare from './views/pages/share.vue'; +import MkFollow from '../common/views/pages/follow.vue'; /** * init @@ -80,7 +81,8 @@ init((launch) => { { path: '/@:user', component: MkUser }, { path: '/@:user/followers', component: MkFollowers }, { path: '/@:user/following', component: MkFollowing }, - { path: '/notes/:note', component: MkNote } + { path: '/notes/:note', component: MkNote }, + { path: '/authorize-follow', component: MkFollow } ] }); @@ -88,7 +90,7 @@ init((launch) => { launch(router, os => ({ chooseDriveFolder, chooseDriveFile, - dialog, + dialog: dialog(os), input, post: post(os), notify diff --git a/src/client/app/mobile/views/components/dialog.vue b/src/client/app/mobile/views/components/dialog.vue new file mode 100644 index 0000000000..9ee01cb782 --- /dev/null +++ b/src/client/app/mobile/views/components/dialog.vue @@ -0,0 +1,171 @@ +<template> +<div class="mk-dialog"> + <div class="bg" ref="bg" @click="onBgClick"></div> + <div class="main" ref="main"> + <header v-html="title" :class="$style.header"></header> + <div class="body" v-html="text"></div> + <div class="buttons"> + <button v-for="button in buttons" @click="click(button)">{{ button.text }}</button> + </div> + </div> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import * as anime from 'animejs'; + +export default Vue.extend({ + props: { + title: { + type: String, + required: false + }, + text: { + type: String, + required: true + }, + buttons: { + type: Array, + default: () => { + return [{ + text: 'OK' + }]; + } + }, + modal: { + type: Boolean, + default: false + } + }, + mounted() { + this.$nextTick(() => { + (this.$refs.bg as any).style.pointerEvents = 'auto'; + anime({ + targets: this.$refs.bg, + opacity: 1, + duration: 100, + easing: 'linear' + }); + + anime({ + targets: this.$refs.main, + opacity: 1, + scale: [1.2, 1], + duration: 300, + easing: [0, 0.5, 0.5, 1] + }); + }); + }, + methods: { + click(button) { + this.$emit('clicked', button.id); + this.close(); + }, + close() { + (this.$refs.bg as any).style.pointerEvents = 'none'; + anime({ + targets: this.$refs.bg, + opacity: 0, + duration: 300, + easing: 'linear' + }); + + (this.$refs.main as any).style.pointerEvents = 'none'; + anime({ + targets: this.$refs.main, + opacity: 0, + scale: 0.8, + duration: 300, + easing: [ 0.5, -0.5, 1, 0.5 ], + complete: () => this.$destroy() + }); + }, + onBgClick() { + if (!this.modal) { + this.close(); + } + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.mk-dialog + > .bg + display block + position fixed + z-index 8192 + top 0 + left 0 + width 100% + height 100% + background rgba(#000, 0.7) + opacity 0 + pointer-events none + + > .main + display block + position fixed + z-index 8192 + top 20% + left 0 + right 0 + margin 0 auto 0 auto + padding 16px + width calc(100% - 32px) + max-width 300px + background #fff + opacity 0 + + > .body + margin 1em 0 + color #888 + + > .buttons + > button + display inline-block + float right + margin 0 + padding 0 10px + font-size 1.1em + font-weight normal + text-decoration none + color #888 + background transparent + outline none + border none + border-radius 0 + cursor pointer + transition color 0.1s ease + + i + margin 0 0.375em + + &:hover + color $theme-color + + &:active + color darken($theme-color, 10%) + transition color 0s ease + +</style> + +<style lang="stylus" module> +@import '~const.styl' + +.header + margin 0 0 1em 0 + color $theme-color + // color #43A4EC + font-weight bold + + &:empty + display none + + > i + margin-right 0.5em + +</style> diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue index f3e77d7062..fa15922183 100644 --- a/src/client/app/mobile/views/components/note-detail.vue +++ b/src/client/app/mobile/views/components/note-detail.vue @@ -38,10 +38,7 @@ <div class="text"> <span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span> <span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span> - <mk-note-html v-if="p.text" :text="p.text" :i="$store.state.i"/> - </div> - <div class="tags" v-if="p.tags && p.tags.length > 0"> - <router-link v-for="tag in p.tags" :key="tag" :to="`/tags/${tag}`">{{ tag }}</router-link> + <misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i"/> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media" :raw="true"/> @@ -83,7 +80,7 @@ <script lang="ts"> import Vue from 'vue'; -import parse from '../../../../../text/parse'; +import parse from '../../../../../mfm/parse'; import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; @@ -197,7 +194,8 @@ export default Vue.extend({ (this as any).os.new(MkReactionPicker, { source: this.$refs.reactButton, note: this.p, - compact: true + compact: true, + big: true }); }, menu() { @@ -369,31 +367,6 @@ root(isDark) display block max-width 100% - > .tags - margin 4px 0 0 0 - - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background #edf0f3 - border-radius 4px - - &:before - content "" - display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background #fff - border-radius 100% - > .time font-size 16px color isDark ? #606984 : #c0c0c0 diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 4498bb5633..8fc8af7f8d 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -25,16 +25,13 @@ <span v-if="p.isHidden" style="opacity: 0.5">(%i18n:@private%)</span> <span v-if="p.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span> <a class="reply" v-if="p.reply">%fa:reply%</a> - <mk-note-html v-if="p.text && !canHideText(p)" :text="p.text" :i="$store.state.i" :class="$style.text"/> + <misskey-flavored-markdown v-if="p.text" :text="p.text" :i="$store.state.i" :class="$style.text"/> <a class="rp" v-if="p.renote != null">RP:</a> </div> <div class="media" v-if="p.media.length > 0"> <mk-media-list :media-list="p.media"/> </div> <mk-poll v-if="p.poll" :note="p" ref="pollViewer"/> - <div class="tags" v-if="p.tags && p.tags.length > 0"> - <router-link v-for="tag in p.tags" :key="tag" :to="`/tags/${tag}`">{{ tag }}</router-link> - </div> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> <a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a> <div class="map" v-if="p.geo" ref="map"></div> @@ -68,8 +65,7 @@ <script lang="ts"> import Vue from 'vue'; -import parse from '../../../../../text/parse'; -import canHideText from '../../../common/scripts/can-hide-text'; +import parse from '../../../../../mfm/parse'; import MkNoteMenu from '../../../common/views/components/note-menu.vue'; import MkReactionPicker from '../../../common/views/components/reaction-picker.vue'; @@ -165,8 +161,6 @@ export default Vue.extend({ }, methods: { - canHideText, - capture(withHandler = false) { if (this.$store.getters.isSignedIn) { this.connection.send({ @@ -216,7 +210,8 @@ export default Vue.extend({ (this as any).os.new(MkReactionPicker, { source: this.$refs.reactButton, note: this.p, - compact: true + compact: true, + big: true }); }, @@ -419,31 +414,6 @@ root(isDark) .mk-url-preview margin-top 8px - > .tags - margin 4px 0 0 0 - - > * - display inline-block - margin 0 8px 0 0 - padding 2px 8px 2px 16px - font-size 90% - color #8d969e - background isDark ? #313543 : #edf0f3 - border-radius 4px - - &:before - content "" - display block - position absolute - top 0 - bottom 0 - left 4px - width 8px - height 8px - margin auto 0 - background isDark ? #282c37 : #fff - border-radius 100% - > .media > img display block diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index 7aaf0424c7..06d22c7258 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -13,7 +13,9 @@ <button @click="resolveInitPromise">%i18n:@retry%</button> </div> - <transition-group name="mk-notes" class="transition"> + <!-- トランジションを有効にするとなぜかメモリリークする --> + <!-- <transition-group name="mk-notes" class="transition"> --> + <div class="transition"> <template v-for="(note, i) in _notes"> <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> @@ -21,7 +23,8 @@ <span>%fa:angle-down%{{ _notes[i + 1]._datetext }}</span> </p> </template> - </transition-group> + </div> + <!-- </transition-group> --> <footer v-if="more"> <button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> diff --git a/src/client/app/mobile/views/components/notifications.vue b/src/client/app/mobile/views/components/notifications.vue index 6bb9e9bb2c..fc220c252a 100644 --- a/src/client/app/mobile/views/components/notifications.vue +++ b/src/client/app/mobile/views/components/notifications.vue @@ -1,6 +1,8 @@ <template> <div class="mk-notifications"> - <transition-group name="mk-notifications" class="transition notifications"> + <!-- トランジションを有効にするとなぜかメモリリークする --> + <!-- <transition-group name="mk-notifications" class="transition notifications"> --> + <div class="transition notifications"> <template v-for="(notification, i) in _notifications"> <mk-notification :notification="notification" :key="notification.id"/> <p class="date" :key="notification.id + '_date'" v-if="i != notifications.length - 1 && notification._date != _notifications[i + 1]._date"> @@ -8,7 +10,8 @@ <span>%fa:angle-down%{{ _notifications[i + 1]._datetext }}</span> </p> </template> - </transition-group> + </div> + <!-- </transition-group> --> <button class="more" v-if="moreNotifications" @click="fetchMoreNotifications" :disabled="fetchingMoreNotifications"> <template v-if="fetchingMoreNotifications">%fa:spinner .pulse .fw%</template> diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 62fa185085..1015a44115 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -45,7 +45,7 @@ import Vue from 'vue'; import * as XDraggable from 'vuedraggable'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import getKao from '../../../common/scripts/get-kao'; -import parse from '../../../../../text/parse'; +import parse from '../../../../../mfm/parse'; import { host } from '../../../config'; export default Vue.extend({ diff --git a/src/client/app/mobile/views/components/sub-note-content.vue b/src/client/app/mobile/views/components/sub-note-content.vue index 4ad90b97df..a4ce49786e 100644 --- a/src/client/app/mobile/views/components/sub-note-content.vue +++ b/src/client/app/mobile/views/components/sub-note-content.vue @@ -4,7 +4,7 @@ <span v-if="note.isHidden" style="opacity: 0.5">(%i18n:@private%)</span> <span v-if="note.deletedAt" style="opacity: 0.5">(%i18n:@deleted%)</span> <a class="reply" v-if="note.replyId">%fa:reply%</a> - <mk-note-html v-if="note.text" :text="note.text" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i"/> <a class="rp" v-if="note.renoteId">RP: ...</a> </div> <details v-if="note.media.length > 0"> diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index 34482fccba..89e5eaff67 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -13,6 +13,7 @@ <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> <ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch> + <ui-switch v-model="$store.state.settings.reversiBoardLabels" @change="onChangeReversiBoardLabels">%i18n:common.show-reversi-board-labels%</ui-switch> <div> <div>%i18n:@timeline%</div> @@ -182,6 +183,13 @@ export default Vue.extend({ }); }, + onChangeReversiBoardLabels(v) { + this.$store.dispatch('settings/set', { + key: 'reversiBoardLabels', + value: v + }); + }, + onChangeShowReplyTarget(v) { this.$store.dispatch('settings/set', { key: 'showReplyTarget', diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue index 3d37015906..ba9de9f8a6 100644 --- a/src/client/app/mobile/views/pages/user.vue +++ b/src/client/app/mobile/views/pages/user.vue @@ -18,7 +18,9 @@ <span class="username"><mk-acct :user="user"/></span> <span class="followed" v-if="user.isFollowed">%i18n:@follows-you%</span> </div> - <div class="description">{{ user.description }}</div> + <div class="description"> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + </div> <div class="info"> <p class="location" v-if="user.host === null && user.profile.location"> %fa:map-marker%{{ user.profile.location }} diff --git a/src/client/app/mobile/views/pages/welcome.vue b/src/client/app/mobile/views/pages/welcome.vue index cd8f5841e7..ec21588ade 100644 --- a/src/client/app/mobile/views/pages/welcome.vue +++ b/src/client/app/mobile/views/pages/welcome.vue @@ -14,6 +14,9 @@ <div class="tl"> <mk-welcome-timeline/> </div> + <div class="hashtags"> + <router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link> + </div> <div class="stats" v-if="stats"> <span>%fa:user% {{ stats.originalUsersCount | number }}</span> <span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span> @@ -37,13 +40,18 @@ export default Vue.extend({ stats: null, host, name, - description + description, + tags: [] }; }, created() { (this as any).api('stats').then(stats => { this.stats = stats; }); + + (this as any).api('hashtags/trend').then(stats => { + this.tags = stats.map(x => x.tag); + }); } }); </script> @@ -116,12 +124,22 @@ export default Vue.extend({ box-shadow 0 1px 3px rgba(#000, 0.075), inset 0 0 5px rgba(#000, 0.2) > .tl + margin 16px 0 + > * max-height 300px border-radius 6px overflow auto -webkit-overflow-scrolling touch + > .hashtags + padding 16px 0 + border solid 2px #ddd + border-radius 8px + + > * + margin 0 16px + > .stats margin 16px 0 padding 8px diff --git a/src/client/app/store.ts b/src/client/app/store.ts index 1bc39ae66d..dfb24bb5f4 100644 --- a/src/client/app/store.ts +++ b/src/client/app/store.ts @@ -19,7 +19,8 @@ const defaultSettings = { loadRemoteMedia: true, disableViaMobile: false, memo: null, - iLikeSushi: false + iLikeSushi: false, + reversiBoardLabels: false }; const defaultDeviceSettings = { diff --git a/src/client/assets/manifest.json b/src/client/assets/manifest.json index dcd1e26790..bae0ee7f1d 100644 --- a/src/client/assets/manifest.json +++ b/src/client/assets/manifest.json @@ -37,6 +37,6 @@ } ], "share_target": { - "url_template": "share?text={title}%20-%20{text}%20-%20{url}" + "url_template": "share?text=【{title}】%0A{text}%0A{url}" } } |