diff --git a/src/web/app/auth/tags/index.tag b/src/web/app/auth/tags/index.tag index 3a24c2d6be..56fbbb7da2 100644 --- a/src/web/app/auth/tags/index.tag +++ b/src/web/app/auth/tags/index.tag @@ -1,5 +1,5 @@ <mk-index> - <main v-if="SIGNIN"> + <main v-if="$root.$data.os.isSignedIn"> <p class="fetching" v-if="fetching">読み込み中<mk-ellipsis/></p> <mk-form ref="form" v-if="state == 'waiting'" session={ session }/> <div class="denied" v-if="state == 'denied'"> @@ -15,7 +15,7 @@ <p>セッションが存在しません。</p> </div> </main> - <main class="signin" v-if="!SIGNIN"> + <main class="signin" v-if="!$root.$data.os.isSignedIn"> <h1>サインインしてください</h1> <mk-signin/> </main> @@ -93,7 +93,7 @@ this.token = window.location.href.split('/').pop(); this.on('mount', () => { - if (!this.SIGNIN) return; + if (!this.$root.$data.os.isSignedIn) return; // Fetch session this.$root.$data.os.api('auth/session/show', { diff --git a/src/web/app/ch/tags/channel.tag b/src/web/app/ch/tags/channel.tag index d95de97376..b5c6ce1e69 100644 --- a/src/web/app/ch/tags/channel.tag +++ b/src/web/app/ch/tags/channel.tag @@ -4,7 +4,7 @@ <main v-if="!fetching"> <h1>{ channel.title }</h1> - <div v-if="SIGNIN"> + <div v-if="$root.$data.os.isSignedIn"> <p v-if="channel.is_watching">このチャンネルをウォッチしています <a @click="unwatch">ウォッチ解除</a></p> <p v-if="!channel.is_watching"><a @click="watch">このチャンネルをウォッチする</a></p> </div> @@ -24,8 +24,8 @@ </div> </div> <hr> - <mk-channel-form v-if="SIGNIN" channel={ channel } ref="form"/> - <div v-if="!SIGNIN"> + <mk-channel-form v-if="$root.$data.os.isSignedIn" channel={ channel } ref="form"/> + <div v-if="!$root.$data.os.isSignedIn"> <p>参加するには<a href={ _URL_ }>ログインまたは新規登録</a>してください</p> </div> <hr> @@ -125,7 +125,7 @@ this.posts.unshift(post); this.update(); - if (document.hidden && this.SIGNIN && post.user_id !== this.$root.$data.os.i.id) { + if (document.hidden && this.$root.$data.os.isSignedIn && post.user_id !== this.$root.$data.os.i.id) { this.unreadCount++; document.title = `(${this.unreadCount}) ${this.channel.title} | Misskey`; } diff --git a/src/web/app/ch/tags/header.tag b/src/web/app/ch/tags/header.tag index 47a1e3e76a..747bec357b 100644 --- a/src/web/app/ch/tags/header.tag +++ b/src/web/app/ch/tags/header.tag @@ -3,8 +3,8 @@ <a href={ _CH_URL_ }>Index</a> | <a href={ _URL_ }>Misskey</a> </div> <div> - <a v-if="!SIGNIN" href={ _URL_ }>ログイン(新規登録)</a> - <a v-if="SIGNIN" href={ _URL_ + '/' + I.username }>{ I.username }</a> + <a v-if="!$root.$data.os.isSignedIn" href={ _URL_ }>ログイン(新規登録)</a> + <a v-if="$root.$data.os.isSignedIn" href={ _URL_ + '/' + I.username }>{ I.username }</a> </div> <style lang="stylus" scoped> :scope diff --git a/src/web/app/desktop/-tags/user-preview.tag b/src/web/app/desktop/-tags/user-preview.tag index 3a65fb79b9..8503e9aeb0 100644 --- a/src/web/app/desktop/-tags/user-preview.tag +++ b/src/web/app/desktop/-tags/user-preview.tag @@ -17,7 +17,7 @@ <p>フォロワー</p><a>{ user.followers_count }</a> </div> </div> - <mk-follow-button v-if="SIGNIN && user.id != I.id" user={ userPromise }/> + <mk-follow-button v-if="$root.$data.os.isSignedIn && user.id != I.id" user={ userPromise }/> </template> <style lang="stylus" scoped> :scope diff --git a/src/web/app/desktop/-tags/users-list.tag b/src/web/app/desktop/-tags/users-list.tag index bf002ae552..03c527109b 100644 --- a/src/web/app/desktop/-tags/users-list.tag +++ b/src/web/app/desktop/-tags/users-list.tag @@ -2,7 +2,7 @@ <nav> <div> <span data-is-active={ mode == 'all' } @click="setMode.bind(this, 'all')">すべて<span>{ opts.count }</span></span> - <span v-if="SIGNIN && opts.youKnowCount" data-is-active={ mode == 'iknow' } @click="setMode.bind(this, 'iknow')">知り合い<span>{ opts.youKnowCount }</span></span> + <span v-if="$root.$data.os.isSignedIn && opts.youKnowCount" data-is-active={ mode == 'iknow' } @click="setMode.bind(this, 'iknow')">知り合い<span>{ opts.youKnowCount }</span></span> </div> </nav> <div class="users" v-if="!fetching && users.length != 0"> diff --git a/src/web/app/mobile/tags/home.tag b/src/web/app/mobile/tags/home.tag deleted file mode 100644 index 038322b633..0000000000 --- a/src/web/app/mobile/tags/home.tag +++ /dev/null @@ -1,23 +0,0 @@ -<mk-home> - <mk-home-timeline ref="tl"/> - <style lang="stylus" scoped> - :scope - display block - - > mk-home-timeline - max-width 600px - margin 0 auto - padding 8px - - @media (min-width 500px) - padding 16px - - </style> - <script lang="typescript"> - this.on('mount', () => { - this.$refs.tl.on('loaded', () => { - this.$emit('loaded'); - }); - }); - </script> -</mk-home> diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag deleted file mode 100644 index b9bb4e17a4..0000000000 --- a/src/web/app/mobile/tags/user.tag +++ /dev/null @@ -1,735 +0,0 @@ -<mk-user> - <div class="user" v-if="!fetching"> - <header> - <div class="banner" style={ user.banner_url ? 'background-image: url(' + user.banner_url + '?thumbnail&size=1024)' : '' }></div> - <div class="body"> - <div class="top"> - <a class="avatar"> - <img src={ user.avatar_url + '?thumbnail&size=200' } alt="avatar"/> - </a> - <mk-follow-button v-if="SIGNIN && I.id != user.id" user={ user }/> - </div> - <div class="title"> - <h1>{ user.name }</h1> - <span class="username">@{ user.username }</span> - <span class="followed" v-if="user.is_followed">%i18n:mobile.tags.mk-user.follows-you%</span> - </div> - <div class="description">{ user.description }</div> - <div class="info"> - <p class="location" v-if="user.profile.location"> - %fa:map-marker%{ user.profile.location } - </p> - <p class="birthday" v-if="user.profile.birthday"> - %fa:birthday-cake%{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' } ({ age(user.profile.birthday) }歳) - </p> - </div> - <div class="status"> - <a> - <b>{ user.posts_count }</b> - <i>%i18n:mobile.tags.mk-user.posts%</i> - </a> - <a href="{ user.username }/following"> - <b>{ user.following_count }</b> - <i>%i18n:mobile.tags.mk-user.following%</i> - </a> - <a href="{ user.username }/followers"> - <b>{ user.followers_count }</b> - <i>%i18n:mobile.tags.mk-user.followers%</i> - </a> - </div> - </div> - <nav> - <a data-is-active={ page == 'overview' } @click="go.bind(null, 'overview')">%i18n:mobile.tags.mk-user.overview%</a> - <a data-is-active={ page == 'posts' } @click="go.bind(null, 'posts')">%i18n:mobile.tags.mk-user.timeline%</a> - <a data-is-active={ page == 'media' } @click="go.bind(null, 'media')">%i18n:mobile.tags.mk-user.media%</a> - </nav> - </header> - <div class="body"> - <mk-user-overview v-if="page == 'overview'" user={ user }/> - <mk-user-timeline v-if="page == 'posts'" user={ user }/> - <mk-user-timeline v-if="page == 'media'" user={ user } with-media={ true }/> - </div> - </div> - <style lang="stylus" scoped> - :scope - display block - - > .user - > header - box-shadow 0 4px 4px rgba(0, 0, 0, 0.3) - - > .banner - padding-bottom 33.3% - background-color #1b1b1b - background-size cover - background-position center - - > .body - padding 12px - margin 0 auto - max-width 600px - - > .top - &:after - content '' - display block - clear both - - > .avatar - display block - float left - width 25% - height 40px - - > img - display block - position absolute - left -2px - bottom -2px - width 100% - border 2px solid #313a42 - border-radius 6px - - @media (min-width 500px) - left -4px - bottom -4px - border 4px solid #313a42 - border-radius 12px - - > mk-follow-button - float right - height 40px - - > .title - margin 8px 0 - - > h1 - margin 0 - line-height 22px - font-size 20px - color #fff - - > .username - display inline-block - line-height 20px - font-size 16px - font-weight bold - color #657786 - - > .followed - margin-left 8px - padding 2px 4px - font-size 12px - color #657786 - background #f8f8f8 - border-radius 4px - - > .description - margin 8px 0 - color #fff - - > .info - margin 8px 0 - - > p - display inline - margin 0 16px 0 0 - color #a9b9c1 - - > i - margin-right 4px - - > .status - > a - color #657786 - - &:not(:last-child) - margin-right 16px - - > b - margin-right 4px - font-size 16px - color #fff - - > i - font-size 14px - - > mk-activity-table - margin 12px 0 0 0 - - > nav - display flex - justify-content center - margin 0 auto - max-width 600px - - > a - display block - flex 1 1 - text-align center - line-height 52px - font-size 14px - text-decoration none - color #657786 - border-bottom solid 2px transparent - - &[data-is-active] - font-weight bold - color $theme-color - border-color $theme-color - - > .body - padding 8px - - @media (min-width 500px) - padding 16px - - </style> - <script lang="typescript"> - this.age = require('s-age'); - - this.mixin('i'); - this.mixin('api'); - - this.username = this.opts.user; - this.page = this.opts.page ? this.opts.page : 'overview'; - this.fetching = true; - - this.on('mount', () => { - this.$root.$data.os.api('users/show', { - username: this.username - }).then(user => { - this.fetching = false; - this.user = user; - this.$emit('loaded', user); - this.update(); - }); - }); - - this.go = page => { - this.update({ - page: page - }); - }; - </script> -</mk-user> - -<mk-user-overview> - <mk-post-detail v-if="user.pinned_post" post={ user.pinned_post } compact={ true }/> - <section class="recent-posts"> - <h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-posts%</h2> - <div> - <mk-user-overview-posts user={ user }/> - </div> - </section> - <section class="images"> - <h2>%fa:image%%i18n:mobile.tags.mk-user-overview.images%</h2> - <div> - <mk-user-overview-photos user={ user }/> - </div> - </section> - <section class="activity"> - <h2>%fa:chart-bar%%i18n:mobile.tags.mk-user-overview.activity%</h2> - <div> - <mk-user-overview-activity-chart user={ user }/> - </div> - </section> - <section class="keywords"> - <h2>%fa:R comment%%i18n:mobile.tags.mk-user-overview.keywords%</h2> - <div> - <mk-user-overview-keywords user={ user }/> - </div> - </section> - <section class="domains"> - <h2>%fa:globe%%i18n:mobile.tags.mk-user-overview.domains%</h2> - <div> - <mk-user-overview-domains user={ user }/> - </div> - </section> - <section class="frequently-replied-users"> - <h2>%fa:users%%i18n:mobile.tags.mk-user-overview.frequently-replied-users%</h2> - <div> - <mk-user-overview-frequently-replied-users user={ user }/> - </div> - </section> - <section class="followers-you-know" v-if="SIGNIN && I.id !== user.id"> - <h2>%fa:users%%i18n:mobile.tags.mk-user-overview.followers-you-know%</h2> - <div> - <mk-user-overview-followers-you-know user={ user }/> - </div> - </section> - <p>%i18n:mobile.tags.mk-user-overview.last-used-at%: <b><mk-time time={ user.last_used_at }/></b></p> - <style lang="stylus" scoped> - :scope - display block - max-width 600px - margin 0 auto - - > mk-post-detail - margin 0 0 8px 0 - - > section - background #eee - border-radius 8px - box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) - - &:not(:last-child) - margin-bottom 8px - - > h2 - margin 0 - padding 8px 10px - font-size 15px - font-weight normal - color #465258 - background #fff - border-radius 8px 8px 0 0 - - > i - margin-right 6px - - > .activity - > div - padding 8px - - > p - display block - margin 16px - text-align center - color #cad2da - - </style> - <script lang="typescript"> - this.mixin('i'); - - this.user = this.opts.user; - </script> -</mk-user-overview> - -<mk-user-overview-posts> - <p class="initializing" v-if="initializing">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-posts.loading%<mk-ellipsis/></p> - <div v-if="!initializing && posts.length > 0"> - <template each={ posts }> - <mk-user-overview-posts-post-card post={ this }/> - </template> - </div> - <p class="empty" v-if="!initializing && posts.length == 0">%i18n:mobile.tags.mk-user-overview-posts.no-posts%</p> - <style lang="stylus" scoped> - :scope - display block - - > div - overflow-x scroll - -webkit-overflow-scrolling touch - white-space nowrap - padding 8px - - > * - vertical-align top - - &:not(:last-child) - margin-right 8px - - > .initializing - > .empty - margin 0 - padding 16px - text-align center - color #aaa - - > i - margin-right 4px - - </style> - <script lang="typescript"> - this.mixin('api'); - - this.user = this.opts.user; - this.initializing = true; - - this.on('mount', () => { - this.$root.$data.os.api('users/posts', { - user_id: this.user.id - }).then(posts => { - this.update({ - posts: posts, - initializing: false - }); - }); - }); - </script> -</mk-user-overview-posts> - -<mk-user-overview-posts-post-card> - <a href={ '/' + post.user.username + '/' + post.id }> - <header> - <img src={ post.user.avatar_url + '?thumbnail&size=64' } alt="avatar"/><h3>{ post.user.name }</h3> - </header> - <div> - { text } - </div> - <mk-time time={ post.created_at }/> - </a> - <style lang="stylus" scoped> - :scope - display inline-block - width 150px - //height 120px - font-size 12px - background #fff - border-radius 4px - - > a - display block - color #2c3940 - - &:hover - text-decoration none - - > header - > img - position absolute - top 8px - left 8px - width 28px - height 28px - border-radius 6px - - > h3 - display inline-block - overflow hidden - width calc(100% - 45px) - margin 8px 0 0 42px - line-height 28px - white-space nowrap - text-overflow ellipsis - font-size 12px - - > div - padding 2px 8px 8px 8px - height 60px - overflow hidden - white-space normal - - &:after - content "" - display block - position absolute - top 40px - left 0 - width 100% - height 20px - background linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #fff 100%) - - > mk-time - display inline-block - padding 8px - color #aaa - - </style> - <script lang="typescript"> - import summary from '../../../../common/get-post-summary.ts'; - - this.post = this.opts.post; - this.text = summary(this.post); - </script> -</mk-user-overview-posts-post-card> - -<mk-user-overview-photos> - <p class="initializing" v-if="initializing">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-photos.loading%<mk-ellipsis/></p> - <div class="stream" v-if="!initializing && images.length > 0"> - <template each={ image in images }> - <a class="img" style={ 'background-image: url(' + image.media.url + '?thumbnail&size=256)' } href={ '/' + image.post.user.username + '/' + image.post.id }></a> - </template> - </div> - <p class="empty" v-if="!initializing && images.length == 0">%i18n:mobile.tags.mk-user-overview-photos.no-photos%</p> - <style lang="stylus" scoped> - :scope - display block - - > .stream - display -webkit-flex - display -moz-flex - display -ms-flex - display flex - justify-content center - flex-wrap wrap - padding 8px - - > .img - flex 1 1 33% - width 33% - height 80px - background-position center center - background-size cover - background-clip content-box - border solid 2px transparent - border-radius 4px - - > .initializing - > .empty - margin 0 - padding 16px - text-align center - color #aaa - - > i - margin-right 4px - - </style> - <script lang="typescript"> - this.mixin('api'); - - this.images = []; - this.initializing = true; - this.user = this.opts.user; - - this.on('mount', () => { - this.$root.$data.os.api('users/posts', { - user_id: this.user.id, - with_media: true, - limit: 6 - }).then(posts => { - this.initializing = false; - posts.forEach(post => { - post.media.forEach(media => { - if (this.images.length < 9) this.images.push({ - post, - media - }); - }); - }); - this.update(); - }); - }); - </script> -</mk-user-overview-photos> - -<mk-user-overview-activity-chart> - <svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none"> - <g each={ d, i in data.reverse() }> - <rect width="0.8" riot-height={ d.postsH } - riot-x={ i + 0.1 } riot-y={ 1 - d.postsH - d.repliesH - d.repostsH } - fill="#41ddde"/> - <rect width="0.8" riot-height={ d.repliesH } - riot-x={ i + 0.1 } riot-y={ 1 - d.repliesH - d.repostsH } - fill="#f7796c"/> - <rect width="0.8" riot-height={ d.repostsH } - riot-x={ i + 0.1 } riot-y={ 1 - d.repostsH } - fill="#a1de41"/> - </g> - </svg> - <style lang="stylus" scoped> - :scope - display block - max-width 600px - margin 0 auto - - > svg - display block - width 100% - height 80px - - > rect - transform-origin center - - </style> - <script lang="typescript"> - this.mixin('api'); - - this.user = this.opts.user; - - this.on('mount', () => { - this.$root.$data.os.api('aggregation/users/activity', { - user_id: this.user.id, - limit: 30 - }).then(data => { - data.forEach(d => d.total = d.posts + d.replies + d.reposts); - this.peak = Math.max.apply(null, data.map(d => d.total)); - data.forEach(d => { - d.postsH = d.posts / this.peak; - d.repliesH = d.replies / this.peak; - d.repostsH = d.reposts / this.peak; - }); - this.update({ data }); - }); - }); - </script> -</mk-user-overview-activity-chart> - -<mk-user-overview-keywords> - <div v-if="user.keywords != null && user.keywords.length > 1"> - <template each={ keyword in user.keywords }> - <a>{ keyword }</a> - </template> - </div> - <p class="empty" v-if="user.keywords == null || user.keywords.length == 0">%i18n:mobile.tags.mk-user-overview-keywords.no-keywords%</p> - <style lang="stylus" scoped> - :scope - display block - - > div - padding 4px - - > a - display inline-block - margin 4px - color #555 - - > .empty - margin 0 - padding 16px - text-align center - color #aaa - - > i - margin-right 4px - - </style> - <script lang="typescript"> - this.user = this.opts.user; - </script> -</mk-user-overview-keywords> - -<mk-user-overview-domains> - <div v-if="user.domains != null && user.domains.length > 1"> - <template each={ domain in user.domains }> - <a style="opacity: { 0.5 + (domain.weight / 2) }">{ domain.domain }</a> - </template> - </div> - <p class="empty" v-if="user.domains == null || user.domains.length == 0">%i18n:mobile.tags.mk-user-overview-domains.no-domains%</p> - <style lang="stylus" scoped> - :scope - display block - - > div - padding 4px - - > a - display inline-block - margin 4px - color #555 - - > .empty - margin 0 - padding 16px - text-align center - color #aaa - - > i - margin-right 4px - - </style> - <script lang="typescript"> - this.user = this.opts.user; - </script> -</mk-user-overview-domains> - -<mk-user-overview-frequently-replied-users> - <p class="initializing" v-if="initializing">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-frequently-replied-users.loading%<mk-ellipsis/></p> - <div v-if="!initializing && users.length > 0"> - <template each={ users }> - <mk-user-card user={ this.user }/> - </template> - </div> - <p class="empty" v-if="!initializing && users.length == 0">%i18n:mobile.tags.mk-user-overview-frequently-replied-users.no-users%</p> - <style lang="stylus" scoped> - :scope - display block - - > div - overflow-x scroll - -webkit-overflow-scrolling touch - white-space nowrap - padding 8px - - > mk-user-card - &:not(:last-child) - margin-right 8px - - > .initializing - > .empty - margin 0 - padding 16px - text-align center - color #aaa - - > i - margin-right 4px - - </style> - <script lang="typescript"> - this.mixin('api'); - - this.user = this.opts.user; - this.initializing = true; - - this.on('mount', () => { - this.$root.$data.os.api('users/get_frequently_replied_users', { - user_id: this.user.id - }).then(x => { - this.update({ - users: x, - initializing: false - }); - }); - }); - </script> -</mk-user-overview-frequently-replied-users> - -<mk-user-overview-followers-you-know> - <p class="initializing" v-if="initializing">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p> - <div v-if="!initializing && users.length > 0"> - <template each={ user in users }> - <a href={ '/' + user.username }><img src={ user.avatar_url + '?thumbnail&size=64' } alt={ user.name }/></a> - </template> - </div> - <p class="empty" v-if="!initializing && users.length == 0">%i18n:mobile.tags.mk-user-overview-followers-you-know.no-users%</p> - <style lang="stylus" scoped> - :scope - display block - - > div - padding 4px - - > a - display inline-block - margin 4px - - > img - width 48px - height 48px - vertical-align bottom - border-radius 100% - - > .initializing - > .empty - margin 0 - padding 16px - text-align center - color #aaa - - > i - margin-right 4px - - </style> - <script lang="typescript"> - this.mixin('api'); - - this.user = this.opts.user; - this.initializing = true; - - this.on('mount', () => { - this.$root.$data.os.api('users/followers', { - user_id: this.user.id, - iknow: true, - limit: 30 - }).then(x => { - this.update({ - users: x.users, - initializing: false - }); - }); - }); - </script> -</mk-user-overview-followers-you-know> diff --git a/src/web/app/mobile/tags/users-list.tag b/src/web/app/mobile/tags/users-list.tag index 2bc0c6e939..84427a18e2 100644 --- a/src/web/app/mobile/tags/users-list.tag +++ b/src/web/app/mobile/tags/users-list.tag @@ -1,7 +1,7 @@ <mk-users-list> <nav> <span data-is-active={ mode == 'all' } @click="setMode.bind(this, 'all')">%i18n:mobile.tags.mk-users-list.all%<span>{ opts.count }</span></span> - <span v-if="SIGNIN && opts.youKnowCount" data-is-active={ mode == 'iknow' } @click="setMode.bind(this, 'iknow')">%i18n:mobile.tags.mk-users-list.known%<span>{ opts.youKnowCount }</span></span> + <span v-if="$root.$data.os.isSignedIn && opts.youKnowCount" data-is-active={ mode == 'iknow' } @click="setMode.bind(this, 'iknow')">%i18n:mobile.tags.mk-users-list.known%<span>{ opts.youKnowCount }</span></span> </nav> <div class="users" v-if="!fetching && users.length != 0"> <mk-user-preview each={ users } user={ this }/> diff --git a/src/web/app/mobile/views/components/home.vue b/src/web/app/mobile/views/components/home.vue new file mode 100644 index 0000000000..3feab581d2 --- /dev/null +++ b/src/web/app/mobile/views/components/home.vue @@ -0,0 +1,29 @@ +<template> +<div class="mk-home"> + <mk-timeline @loaded="onTlLoaded"/> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + methods: { + onTlLoaded() { + this.$emit('loaded'); + } + } +}); +</script> + +<style lang="stylus" scoped> +.mk-home + + > .mk-timeline + max-width 600px + margin 0 auto + padding 8px + + @media (min-width 500px) + padding 16px + +</style> diff --git a/src/web/app/mobile/views/components/post-card.vue b/src/web/app/mobile/views/components/post-card.vue new file mode 100644 index 0000000000..4dd6ceb282 --- /dev/null +++ b/src/web/app/mobile/views/components/post-card.vue @@ -0,0 +1,85 @@ +<template> +<div class="mk-post-card"> + <a :href="`/${post.user.username}/${post.id}`"> + <header> + <img :src="`${post.user.avatar_url}?thumbnail&size=64`" alt="avatar"/><h3>{{ post.user.name }}</h3> + </header> + <div> + {{ text }} + </div> + <mk-time :time="post.created_at"/> + </a> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import summary from '../../../../../common/get-post-summary'; + +export default Vue.extend({ + props: ['post'], + computed: { + text(): string { + return summary(this.post); + } + } +}); +</script> + +<style lang="stylus" scoped> +.mk-post-card + display inline-block + width 150px + //height 120px + font-size 12px + background #fff + border-radius 4px + + > a + display block + color #2c3940 + + &:hover + text-decoration none + + > header + > img + position absolute + top 8px + left 8px + width 28px + height 28px + border-radius 6px + + > h3 + display inline-block + overflow hidden + width calc(100% - 45px) + margin 8px 0 0 42px + line-height 28px + white-space nowrap + text-overflow ellipsis + font-size 12px + + > div + padding 2px 8px 8px 8px + height 60px + overflow hidden + white-space normal + + &:after + content "" + display block + position absolute + top 40px + left 0 + width 100% + height 20px + background linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, #fff 100%) + + > mk-time + display inline-block + padding 8px + color #aaa + +</style> diff --git a/src/web/app/mobile/views/components/ui-nav.vue b/src/web/app/mobile/views/components/ui-nav.vue index 3765ce8870..cab24787d1 100644 --- a/src/web/app/mobile/views/components/ui-nav.vue +++ b/src/web/app/mobile/views/components/ui-nav.vue @@ -2,7 +2,7 @@ <div class="mk-ui-nav" :style="{ display: isOpen ? 'block' : 'none' }"> <div class="backdrop" @click="parent.toggleDrawer"></div> <div class="body"> - <a class="me" v-if="SIGNIN" href={ '/' + I.username }> + <a class="me" v-if="$root.$data.os.isSignedIn" href={ '/' + I.username }> <img class="avatar" src={ I.avatar_url + '?thumbnail&size=128' } alt="avatar"/> <p class="name">{ I.name }</p> </a> diff --git a/src/web/app/mobile/views/pages/user.vue b/src/web/app/mobile/views/pages/user.vue new file mode 100644 index 0000000000..d92f3bbe65 --- /dev/null +++ b/src/web/app/mobile/views/pages/user.vue @@ -0,0 +1,226 @@ +<template> +<mk-ui :func="fn" func-icon="%fa:pencil-alt%"> + <span slot="header">%fa:user% {{user.name}}</span> + <div v-if="!fetching" :class="$style.user"> + <header> + <div class="banner" :style="user.banner_url ? `background-image: url(${user.banner_url}?thumbnail&size=1024)` : ''"></div> + <div class="body"> + <div class="top"> + <a class="avatar"> + <img :src="`${user.avatar_url}?thumbnail&size=200`" alt="avatar"/> + </a> + <mk-follow-button v-if="$root.$data.os.isSignedIn && $root.$data.os.i.id != user.id" :user="user"/> + </div> + <div class="title"> + <h1>{{ user.name }}</h1> + <span class="username">@{{ user.username }}</span> + <span class="followed" v-if="user.is_followed">%i18n:mobile.tags.mk-user.follows-you%</span> + </div> + <div class="description">{{ user.description }}</div> + <div class="info"> + <p class="location" v-if="user.profile.location"> + %fa:map-marker%{{ user.profile.location }} + </p> + <p class="birthday" v-if="user.profile.birthday"> + %fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳) + </p> + </div> + <div class="status"> + <a> + <b>{{ user.posts_count }}</b> + <i>%i18n:mobile.tags.mk-user.posts%</i> + </a> + <a :href="`${user.username}/following`"> + <b>{{ user.following_count }}</b> + <i>%i18n:mobile.tags.mk-user.following%</i> + </a> + <a :href="`${user.username}/followers`"> + <b>{{ user.followers_count }}</b> + <i>%i18n:mobile.tags.mk-user.followers%</i> + </a> + </div> + </div> + <nav> + <a :data-is-active=" page == 'home' " @click="page = 'home'">%i18n:mobile.tags.mk-user.overview%</a> + <a :data-is-active=" page == 'posts' " @click="page = 'posts'">%i18n:mobile.tags.mk-user.timeline%</a> + <a :data-is-active=" page == 'media' " @click="page = 'media'">%i18n:mobile.tags.mk-user.media%</a> + </nav> + </header> + <div class="body"> + <mk-user-home v-if="page == 'home'" :user="user"/> + <mk-user-timeline v-if="page == 'posts'" :user="user"/> + <mk-user-timeline v-if="page == 'media'" :user="user" with-media/> + </div> + </div> +</mk-ui> +</template> + +<script lang="ts"> +import Vue from 'vue'; +const age = require('s-age'); + +export default Vue.extend({ + props: { + username: { + type: String, + required: true + }, + page: { + default: 'home' + } + }, + data() { + return { + fetching: true, + user: null + }; + }, + computed: { + age(): number { + return age(this.user.profile.birthday); + } + }, + mounted() { + this.$root.$data.os.api('users/show', { + username: this.username + }).then(user => { + this.fetching = false; + this.user = user; + this.$emit('loaded', user); + }); + } +}); +</script> + +<style lang="stylus" module> +.user + > header + box-shadow 0 4px 4px rgba(0, 0, 0, 0.3) + + > .banner + padding-bottom 33.3% + background-color #1b1b1b + background-size cover + background-position center + + > .body + padding 12px + margin 0 auto + max-width 600px + + > .top + &:after + content '' + display block + clear both + + > .avatar + display block + float left + width 25% + height 40px + + > img + display block + position absolute + left -2px + bottom -2px + width 100% + border 2px solid #313a42 + border-radius 6px + + @media (min-width 500px) + left -4px + bottom -4px + border 4px solid #313a42 + border-radius 12px + + > mk-follow-button + float right + height 40px + + > .title + margin 8px 0 + + > h1 + margin 0 + line-height 22px + font-size 20px + color #fff + + > .username + display inline-block + line-height 20px + font-size 16px + font-weight bold + color #657786 + + > .followed + margin-left 8px + padding 2px 4px + font-size 12px + color #657786 + background #f8f8f8 + border-radius 4px + + > .description + margin 8px 0 + color #fff + + > .info + margin 8px 0 + + > p + display inline + margin 0 16px 0 0 + color #a9b9c1 + + > i + margin-right 4px + + > .status + > a + color #657786 + + &:not(:last-child) + margin-right 16px + + > b + margin-right 4px + font-size 16px + color #fff + + > i + font-size 14px + + > mk-activity-table + margin 12px 0 0 0 + + > nav + display flex + justify-content center + margin 0 auto + max-width 600px + + > a + display block + flex 1 1 + text-align center + line-height 52px + font-size 14px + text-decoration none + color #657786 + border-bottom solid 2px transparent + + &[data-is-active] + font-weight bold + color $theme-color + border-color $theme-color + + > .body + padding 8px + + @media (min-width 500px) + padding 16px + +</style> diff --git a/src/web/app/mobile/views/pages/user/followers-you-know.vue b/src/web/app/mobile/views/pages/user/followers-you-know.vue new file mode 100644 index 0000000000..a4358f5d90 --- /dev/null +++ b/src/web/app/mobile/views/pages/user/followers-you-know.vue @@ -0,0 +1,62 @@ +<template> +<div class="mk-user-home-followers-you-know"> + <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p> + <div v-if="!fetching && users.length > 0"> + <a v-for="user in users" :key="user.id" :href="`/${user.username}`"> + <img :src="`${user.avatar_url}?thumbnail&size=64`" :alt="user.name"/> + </a> + </div> + <p class="empty" v-if="!fetching && users.length == 0">%i18n:mobile.tags.mk-user-overview-followers-you-know.no-users%</p> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['user'], + data() { + return { + fetching: true, + users: [] + }; + }, + mounted() { + this.$root.$data.os.api('users/followers', { + user_id: this.user.id, + iknow: true, + limit: 30 + }).then(res => { + this.fetching = false; + this.users = res.users; + }); + } +}); +</script> + +<style lang="stylus" scoped> +.mk-user-home-followers-you-know + + > div + padding 4px + + > a + display inline-block + margin 4px + + > img + width 48px + height 48px + vertical-align bottom + border-radius 100% + + > .initializing + > .empty + margin 0 + padding 16px + text-align center + color #aaa + + > i + margin-right 4px + +</style> diff --git a/src/web/app/mobile/views/pages/user/home-activity.vue b/src/web/app/mobile/views/pages/user/home-activity.vue new file mode 100644 index 0000000000..00a2dafc12 --- /dev/null +++ b/src/web/app/mobile/views/pages/user/home-activity.vue @@ -0,0 +1,62 @@ +<template> +<div class="mk-user-home-activity"> + <svg v-if="data" ref="canvas" viewBox="0 0 30 1" preserveAspectRatio="none"> + <g v-for="(d, i) in data.reverse()" :key="i"> + <rect width="0.8" :height="d.postsH" + :x="i + 0.1" :y="1 - d.postsH - d.repliesH - d.repostsH" + fill="#41ddde"/> + <rect width="0.8" :height="d.repliesH" + :x="i + 0.1" :y="1 - d.repliesH - d.repostsH" + fill="#f7796c"/> + <rect width="0.8" :height="d.repostsH" + :x="i + 0.1" :y="1 - d.repostsH" + fill="#a1de41"/> + </g> + </svg> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['user'], + data() { + return { + fetching: true, + data: [], + peak: null + }; + }, + mounted() { + this.$root.$data.os.api('aggregation/users/activity', { + user_id: this.user.id, + limit: 30 + }).then(data => { + data.forEach(d => d.total = d.posts + d.replies + d.reposts); + this.peak = Math.max.apply(null, data.map(d => d.total)); + data.forEach(d => { + d.postsH = d.posts / this.peak; + d.repliesH = d.replies / this.peak; + d.repostsH = d.reposts / this.peak; + }); + this.data = data; + }); + } +}); +</script> + +<style lang="stylus" scoped> +.mk-user-home-activity + display block + max-width 600px + margin 0 auto + + > svg + display block + width 100% + height 80px + + > rect + transform-origin center + +</style> diff --git a/src/web/app/mobile/views/pages/user/home-friends.vue b/src/web/app/mobile/views/pages/user/home-friends.vue new file mode 100644 index 0000000000..2a7e8b9617 --- /dev/null +++ b/src/web/app/mobile/views/pages/user/home-friends.vue @@ -0,0 +1,54 @@ +<template> +<div class="mk-user-home-friends"> + <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-frequently-replied-users.loading%<mk-ellipsis/></p> + <div v-if="!fetching && users.length > 0"> + <mk-user-card v-for="user in users" :key="user.id" :user="user"/> + </div> + <p class="empty" v-if="!fetching && users.length == 0">%i18n:mobile.tags.mk-user-overview-frequently-replied-users.no-users%</p> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['user'], + data() { + return { + fetching: true, + users: [] + }; + }, + mounted() { + this.$root.$data.os.api('users/get_frequently_replied_users', { + user_id: this.user.id + }).then(res => { + this.fetching = false; + this.users = res.map(x => x.user); + }); + } +}); +</script> + +<style lang="stylus" scoped> +.mk-user-home-friends + > div + overflow-x scroll + -webkit-overflow-scrolling touch + white-space nowrap + padding 8px + + > mk-user-card + &:not(:last-child) + margin-right 8px + + > .initializing + > .empty + margin 0 + padding 16px + text-align center + color #aaa + + > i + margin-right 4px + +</style> diff --git a/src/web/app/mobile/views/pages/user/home-photos.vue b/src/web/app/mobile/views/pages/user/home-photos.vue new file mode 100644 index 0000000000..fc2d0e139a --- /dev/null +++ b/src/web/app/mobile/views/pages/user/home-photos.vue @@ -0,0 +1,78 @@ +<template> +<div class="mk-user-home-photos"> + <p class="initializing" v-if="fetching">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-photos.loading%<mk-ellipsis/></p> + <div class="stream" v-if="!fetching && images.length > 0"> + <a v-for="image in images" :key="image.id" + class="img" + :style="`background-image: url(${image.media.url}?thumbnail&size=256)`" + :href="`/${image.post.user.username}/${image.post.id}`" + ></a> + </div> + <p class="empty" v-if="!fetching && images.length == 0">%i18n:mobile.tags.mk-user-overview-photos.no-photos%</p> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['user'], + data() { + return { + fetching: true, + images: [] + }; + }, + mounted() { + this.$root.$data.os.api('users/posts', { + user_id: this.user.id, + with_media: true, + limit: 6 + }).then(posts => { + this.fetching = false; + posts.forEach(post => { + post.media.forEach(media => { + if (this.images.length < 9) this.images.push({ + post, + media + }); + }); + }); + }); + } +}); +</script> + +<style lang="stylus" scoped> +.mk-user-home-photos + + > .stream + display -webkit-flex + display -moz-flex + display -ms-flex + display flex + justify-content center + flex-wrap wrap + padding 8px + + > .img + flex 1 1 33% + width 33% + height 80px + background-position center center + background-size cover + background-clip content-box + border solid 2px transparent + border-radius 4px + + > .initializing + > .empty + margin 0 + padding 16px + text-align center + color #aaa + + > i + margin-right 4px + +</style> + diff --git a/src/web/app/mobile/views/pages/user/home-posts.vue b/src/web/app/mobile/views/pages/user/home-posts.vue new file mode 100644 index 0000000000..b1451b0887 --- /dev/null +++ b/src/web/app/mobile/views/pages/user/home-posts.vue @@ -0,0 +1,57 @@ +<template> +<div class="mk-user-home-posts"> + <p class="initializing" v-if="initializing">%fa:spinner .pulse .fw%%i18n:mobile.tags.mk-user-overview-posts.loading%<mk-ellipsis/></p> + <div v-if="!initializing && posts.length > 0"> + <mk-post-card v-for="post in posts" :key="post.id" :post="post"/> + </div> + <p class="empty" v-if="!initializing && posts.length == 0">%i18n:mobile.tags.mk-user-overview-posts.no-posts%</p> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['user'], + data() { + return { + fetching: true, + posts: [] + }; + }, + mounted() { + this.$root.$data.os.api('users/posts', { + user_id: this.user.id + }).then(posts => { + this.fetching = false; + this.posts = posts; + }); + } +}); +</script> + +<style lang="stylus" scoped> +.mk-user-home-posts + + > div + overflow-x scroll + -webkit-overflow-scrolling touch + white-space nowrap + padding 8px + + > * + vertical-align top + + &:not(:last-child) + margin-right 8px + + > .initializing + > .empty + margin 0 + padding 16px + text-align center + color #aaa + + > i + margin-right 4px + +</style> diff --git a/src/web/app/mobile/views/pages/user/home.vue b/src/web/app/mobile/views/pages/user/home.vue new file mode 100644 index 0000000000..56b9285591 --- /dev/null +++ b/src/web/app/mobile/views/pages/user/home.vue @@ -0,0 +1,95 @@ +<template> +<div class="mk-user-home"> + <mk-post-detail v-if="user.pinned_post" :post="user.pinned_post" compact/> + <section class="recent-posts"> + <h2>%fa:R comments%%i18n:mobile.tags.mk-user-overview.recent-posts%</h2> + <div> + <mk-user-home-posts :user="user"/> + </div> + </section> + <section class="images"> + <h2>%fa:image%%i18n:mobile.tags.mk-user-overview.images%</h2> + <div> + <mk-user-home-photos :user="user"/> + </div> + </section> + <section class="activity"> + <h2>%fa:chart-bar%%i18n:mobile.tags.mk-user-overview.activity%</h2> + <div> + <mk-user-home-activity-chart :user="user"/> + </div> + </section> + <section class="keywords"> + <h2>%fa:R comment%%i18n:mobile.tags.mk-user-overview.keywords%</h2> + <div> + <mk-user-home-keywords :user="user"/> + </div> + </section> + <section class="domains"> + <h2>%fa:globe%%i18n:mobile.tags.mk-user-overview.domains%</h2> + <div> + <mk-user-home-domains :user="user"/> + </div> + </section> + <section class="frequently-replied-users"> + <h2>%fa:users%%i18n:mobile.tags.mk-user-overview.frequently-replied-users%</h2> + <div> + <mk-user-home-frequently-replied-users :user="user"/> + </div> + </section> + <section class="followers-you-know" v-if="$root.$data.os.isSignedIn && $root.$data.os.i.id !== user.id"> + <h2>%fa:users%%i18n:mobile.tags.mk-user-overview.followers-you-know%</h2> + <div> + <mk-user-home-followers-you-know :user="user"/> + </div> + </section> + <p>%i18n:mobile.tags.mk-user-overview.last-used-at%: <b><mk-time :time="user.last_used_at"/></b></p> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +export default Vue.extend({ + props: ['user'] +}); +</script> + +<style lang="stylus" scoped> +.mk-user-home + max-width 600px + margin 0 auto + + > mk-post-detail + margin 0 0 8px 0 + + > section + background #eee + border-radius 8px + box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) + + &:not(:last-child) + margin-bottom 8px + + > h2 + margin 0 + padding 8px 10px + font-size 15px + font-weight normal + color #465258 + background #fff + border-radius 8px 8px 0 0 + + > i + margin-right 6px + + > .activity + > div + padding 8px + + > p + display block + margin 16px + text-align center + color #cad2da + +</style>