From abc082f7c06bdefe2eae9d7493757e770897d151 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Sun, 24 Jun 2018 13:08:48 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=AD=E3=83=BC=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85=E3=81=99=E3=82=8B=E3=81=AA=E3=81=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja.yml | 7 + src/client/app/common/views/pages/follow.vue | 215 ++++++++++++++++++ src/client/app/desktop/script.ts | 4 +- .../app/desktop/views/pages/user/user.vue | 6 +- src/client/app/mobile/script.ts | 4 +- src/server/activitypub.ts | 16 +- 6 files changed, 232 insertions(+), 20 deletions(-) create mode 100644 src/client/app/common/views/pages/follow.vue diff --git a/locales/ja.yml b/locales/ja.yml index d3d04eb28a..0f1d9cd510 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -280,6 +280,13 @@ common/views/widgets/memo.vue: memo: "ここに書いて!" save: "保存" +common/views/pages/follow.vue: + signed-in-as: "{}としてサインイン中" + following: "フォロー中" + follow: "フォロー" + request-pending: "フォロー許可待ち" + follow-request: "フォロー申請" + desktop/views/components/activity.chart.vue: total: "Black ... Total" notes: "Blue ... Notes" 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/desktop/script.ts b/src/client/app/desktop/script.ts index beb358c7ee..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 } ] }); diff --git a/src/client/app/desktop/views/pages/user/user.vue b/src/client/app/desktop/views/pages/user/user.vue index 0337befdbd..fc5c900037 100644 --- a/src/client/app/desktop/views/pages/user/user.vue +++ b/src/client/app/desktop/views/pages/user/user.vue @@ -1,6 +1,6 @@ <template> <mk-ui> - <div class="zwwan0di1v4356rmdbjmwnn32tptpdp2" v-if="!fetching" :data-darkmode="$store.state.device.darkmode"> + <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> @@ -149,10 +149,10 @@ root(isDark) i color #ccc -.zwwan0di1v4356rmdbjmwnn32tptpdp2[data-darkmode] +.xygkxeaeontfaokvqmiblezmhvhostak[data-darkmode] root(true) -.zwwan0di1v4356rmdbjmwnn32tptpdp2:not([data-darkmode]) +.xygkxeaeontfaokvqmiblezmhvhostak:not([data-darkmode]) root(false) </style> diff --git a/src/client/app/mobile/script.ts b/src/client/app/mobile/script.ts index cfa9654e61..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 } ] }); diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index 1fbc621e91..f8a01a6ffe 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -11,7 +11,7 @@ import renderNote from '../remote/activitypub/renderer/note'; import renderKey from '../remote/activitypub/renderer/key'; import renderPerson from '../remote/activitypub/renderer/person'; import renderOrderedCollection from '../remote/activitypub/renderer/ordered-collection'; -//import parseAcct from '../acct/parse'; +import parseAcct from '../acct/parse'; import config from '../config'; // Init router @@ -142,20 +142,6 @@ router.get('/@:user', async (ctx, next) => { userInfo(ctx, user); }); - -// follow form -router.get('/authorize-follow', async ctx => { - /* TODO - const { username, host } = parseAcct(ctx.query.acct); - if (host === null) { - res.sendStatus(422); - return; - } - - const finger = await request(`https://${host}`) - */ -}); - //#endregion export default router;