diff options
Diffstat (limited to 'src/web/app')
| -rw-r--r-- | src/web/app/common/scripts/home-stream.js | 5 | ||||
| -rw-r--r-- | src/web/app/common/tags/index.js | 1 | ||||
| -rw-r--r-- | src/web/app/common/tags/post-menu.tag | 134 | ||||
| -rw-r--r-- | src/web/app/desktop/tags/post-detail.tag | 23 | ||||
| -rw-r--r-- | src/web/app/desktop/tags/timeline.tag | 21 | ||||
| -rw-r--r-- | src/web/app/mobile/tags/init-following.tag | 2 | ||||
| -rw-r--r-- | src/web/app/mobile/tags/page/post.tag | 14 | ||||
| -rw-r--r-- | src/web/app/mobile/tags/post-detail.tag | 59 | ||||
| -rw-r--r-- | src/web/app/mobile/tags/timeline.tag | 32 | ||||
| -rw-r--r-- | src/web/app/mobile/tags/user.tag | 136 |
10 files changed, 376 insertions, 51 deletions
diff --git a/src/web/app/common/scripts/home-stream.js b/src/web/app/common/scripts/home-stream.js index c54cbd7f19..de9ceb3b51 100644 --- a/src/web/app/common/scripts/home-stream.js +++ b/src/web/app/common/scripts/home-stream.js @@ -12,6 +12,11 @@ class Connection extends Stream { i: me.token }); + // 最終利用日時を更新するため定期的にaliveメッセージを送信 + setInterval(() => { + this.send({ type: 'alive' }); + }, 1000 * 60); + this.on('i_updated', me.update); this.on('my_token_regenerated', () => { diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.js index 1ee8dab42d..35a9f4586e 100644 --- a/src/web/app/common/tags/index.js +++ b/src/web/app/common/tags/index.js @@ -27,3 +27,4 @@ require('./activity-table.tag'); require('./reaction-picker.tag'); require('./reactions-viewer.tag'); require('./reaction-icon.tag'); +require('./post-menu.tag'); diff --git a/src/web/app/common/tags/post-menu.tag b/src/web/app/common/tags/post-menu.tag new file mode 100644 index 0000000000..33895212bc --- /dev/null +++ b/src/web/app/common/tags/post-menu.tag @@ -0,0 +1,134 @@ +<mk-post-menu> + <div class="backdrop" ref="backdrop" onclick={ close }></div> + <div class="popover { compact: opts.compact }" ref="popover"> + <button if={ post.user_id === I.id } onclick={ pin }>%i18n:common.tags.mk-post-menu.pin%</button> + </div> + <style> + $border-color = rgba(27, 31, 35, 0.15) + + :scope + display block + position initial + + > .backdrop + position fixed + top 0 + left 0 + z-index 10000 + width 100% + height 100% + background rgba(0, 0, 0, 0.1) + opacity 0 + + > .popover + position absolute + z-index 10001 + background #fff + border 1px solid $border-color + border-radius 4px + box-shadow 0 3px 12px rgba(27, 31, 35, 0.15) + transform scale(0.5) + opacity 0 + + $balloon-size = 16px + + &:not(.compact) + margin-top $balloon-size + transform-origin center -($balloon-size) + + &:before + content "" + display block + position absolute + top -($balloon-size * 2) + left s('calc(50% - %s)', $balloon-size) + border-top solid $balloon-size transparent + border-left solid $balloon-size transparent + border-right solid $balloon-size transparent + border-bottom solid $balloon-size $border-color + + &:after + content "" + display block + position absolute + top -($balloon-size * 2) + 1.5px + left s('calc(50% - %s)', $balloon-size) + border-top solid $balloon-size transparent + border-left solid $balloon-size transparent + border-right solid $balloon-size transparent + border-bottom solid $balloon-size #fff + + > button + display block + + </style> + <script> + import anime from 'animejs'; + + this.mixin('i'); + this.mixin('api'); + + this.post = this.opts.post; + this.source = this.opts.source; + + this.on('mount', () => { + const rect = this.source.getBoundingClientRect(); + const width = this.refs.popover.offsetWidth; + const height = this.refs.popover.offsetHeight; + if (this.opts.compact) { + const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); + const y = rect.top + window.pageYOffset + (this.source.offsetHeight / 2); + this.refs.popover.style.left = (x - (width / 2)) + 'px'; + this.refs.popover.style.top = (y - (height / 2)) + 'px'; + } else { + const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); + const y = rect.top + window.pageYOffset + this.source.offsetHeight; + this.refs.popover.style.left = (x - (width / 2)) + 'px'; + this.refs.popover.style.top = y + 'px'; + } + + anime({ + targets: this.refs.backdrop, + opacity: 1, + duration: 100, + easing: 'linear' + }); + + anime({ + targets: this.refs.popover, + opacity: 1, + scale: [0.5, 1], + duration: 500 + }); + }); + + this.pin = () => { + this.api('i/pin', { + post_id: this.post.id + }).then(() => { + if (this.opts.cb) this.opts.cb('pinned', '%i18n:common.tags.mk-post-menu.pinned%'); + this.unmount(); + }); + }; + + this.close = () => { + this.refs.backdrop.style.pointerEvents = 'none'; + anime({ + targets: this.refs.backdrop, + opacity: 0, + duration: 200, + easing: 'linear' + }); + + this.refs.popover.style.pointerEvents = 'none'; + anime({ + targets: this.refs.popover, + opacity: 0, + scale: 0.5, + duration: 200, + easing: 'easeInBack', + complete: () => this.unmount() + }); + }; + </script> +</mk-post-menu> diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag index 7a90dccf39..58343482d0 100644 --- a/src/web/app/desktop/tags/post-detail.tag +++ b/src/web/app/desktop/tags/post-detail.tag @@ -43,16 +43,18 @@ </div> <footer> <mk-reactions-viewer post={ p }/> - <button onclick={ reply } title="返信"><i class="fa fa-reply"></i> - <p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> + <button onclick={ reply } title="返信"> + <i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> </button> - <button onclick={ repost } title="Repost"><i class="fa fa-retweet"></i> - <p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> + <button onclick={ repost } title="Repost"> + <i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> </button> - <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="リアクション"><i class="fa fa-plus"></i> - <p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="リアクション"> + <i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + </button> + <button onclick={ menu } ref="menuButton"> + <i class="fa fa-ellipsis-h"></i> </button> - <button><i class="fa fa-ellipsis-h"></i></button> </footer> </article> <div class="replies"> @@ -315,6 +317,13 @@ }); }; + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p + }); + }; + this.loadContext = () => { this.contextFetching = true; diff --git a/src/web/app/desktop/tags/timeline.tag b/src/web/app/desktop/tags/timeline.tag index bce27cd7f3..cd7ac7d207 100644 --- a/src/web/app/desktop/tags/timeline.tag +++ b/src/web/app/desktop/tags/timeline.tag @@ -128,16 +128,16 @@ </div> <footer> <mk-reactions-viewer post={ p } ref="reactionsViewer"/> - <button onclick={ reply } title="%i18n:desktop.tags.mk-timeline-post.reply%"><i class="fa fa-reply"></i> - <p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> + <button onclick={ reply } title="%i18n:desktop.tags.mk-timeline-post.reply%"> + <i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> </button> - <button onclick={ repost } title="%i18n:desktop.tags.mk-timeline-post.repost%"><i class="fa fa-retweet"></i> - <p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> + <button onclick={ repost } title="%i18n:desktop.tags.mk-timeline-post.repost%"> + <i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> </button> - <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:desktop.tags.mk-timeline-post.add-reaction%"><i class="fa fa-plus"></i> - <p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:desktop.tags.mk-timeline-post.add-reaction%"> + <i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> </button> - <button> + <button onclick={ menu } ref="menuButton"> <i class="fa fa-ellipsis-h"></i> </button> <button onclick={ toggleDetail } title="%i18n:desktop.tags.mk-timeline-post.detail"> @@ -525,6 +525,13 @@ }); }; + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p + }); + }; + this.toggleDetail = () => { this.update({ isDetailOpened: !this.isDetailOpened diff --git a/src/web/app/mobile/tags/init-following.tag b/src/web/app/mobile/tags/init-following.tag index 6db9d89b61..d0b63ff5db 100644 --- a/src/web/app/mobile/tags/init-following.tag +++ b/src/web/app/mobile/tags/init-following.tag @@ -67,7 +67,7 @@ > .name display block - margin 24px 0 2px 0 + margin 24px 0 0 0 font-size 16px color #555 diff --git a/src/web/app/mobile/tags/page/post.tag b/src/web/app/mobile/tags/page/post.tag index 198acf1798..6888229f89 100644 --- a/src/web/app/mobile/tags/page/post.tag +++ b/src/web/app/mobile/tags/page/post.tag @@ -2,7 +2,9 @@ <mk-ui ref="ui"> <main if={ !parent.fetching }> <a if={ parent.post.next } href={ parent.post.next }><i class="fa fa-angle-up"></i>%i18n:mobile.tags.mk-post-page.next%</a> - <mk-post-detail ref="post" post={ parent.post }/> + <div> + <mk-post-detail ref="post" post={ parent.post }/> + </div> <a if={ parent.post.prev } href={ parent.post.prev }><i class="fa fa-angle-down"></i>%i18n:mobile.tags.mk-post-page.prev%</a> </main> </mk-ui> @@ -13,6 +15,16 @@ main text-align center + > div + margin 8px auto + padding 0 + max-width 500px + width calc(100% - 16px) + + @media (min-width 500px) + margin 16px auto + width calc(100% - 32px) + > a display inline-block diff --git a/src/web/app/mobile/tags/post-detail.tag b/src/web/app/mobile/tags/post-detail.tag index 9215bafdbc..dc032fe964 100644 --- a/src/web/app/mobile/tags/post-detail.tag +++ b/src/web/app/mobile/tags/post-detail.tag @@ -38,24 +38,26 @@ </div> <mk-poll if={ p.poll } post={ p }/> </div> - <a class="time" href={ url }> + <a class="time" href={ '/' + p.user.username + '/' + p.id }> <mk-time time={ p.created_at } mode="detail"/> </a> <footer> <mk-reactions-viewer post={ p }/> - <button onclick={ reply } title="%i18n:mobile.tags.mk-post-detail.reply%"><i class="fa fa-reply"></i> - <p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> + <button onclick={ reply } title="%i18n:mobile.tags.mk-post-detail.reply%"> + <i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> </button> - <button onclick={ repost } title="Repost"><i class="fa fa-retweet"></i> - <p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> + <button onclick={ repost } title="Repost"> + <i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> </button> - <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%"><i class="fa fa-plus"></i> - <p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton" title="%i18n:mobile.tags.mk-post-detail.reaction%"> + <i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + </button> + <button onclick={ menu } ref="menuButton"> + <i class="fa fa-ellipsis-h"></i> </button> - <button><i class="fa fa-ellipsis-h"></i></button> </footer> </article> - <div class="replies"> + <div class="replies" if={ !compact }> <virtual each={ post in replies }> <mk-post-detail-sub post={ post }/> </virtual> @@ -64,19 +66,14 @@ :scope display block overflow hidden - margin 8px auto + margin 0 auto padding 0 - max-width 500px - width calc(100% - 16px) + width 100% text-align left background #fff border-radius 8px box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) - @media (min-width 500px) - margin 16px auto - width calc(100% - 32px) - > .fetching padding 64px 0 @@ -237,7 +234,7 @@ font-size 1.2em > button - margin 0 28px 0 0 + margin 0 padding 8px background transparent border none @@ -246,6 +243,9 @@ color #ddd cursor pointer + &:not(:last-child) + margin-right 28px + &:hover color #666 @@ -269,6 +269,7 @@ this.mixin('api'); + this.compact = this.opts.compact; this.post = this.opts.post; this.isRepost = this.post.repost != null; this.p = this.isRepost ? this.post.repost : this.post; @@ -299,14 +300,16 @@ } // Get replies - this.api('posts/replies', { - post_id: this.p.id, - limit: 8 - }).then(replies => { - this.update({ - replies: replies + if (!this.compact) { + this.api('posts/replies', { + post_id: this.p.id, + limit: 8 + }).then(replies => { + this.update({ + replies: replies + }); }); - }); + } }); this.reply = () => { @@ -332,6 +335,14 @@ }); }; + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p, + compact: true + }); + }; + this.loadContext = () => { this.contextFetching = true; diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag index 43470d197e..2b0948ac34 100644 --- a/src/web/app/mobile/tags/timeline.tag +++ b/src/web/app/mobile/tags/timeline.tag @@ -181,14 +181,17 @@ </div> <footer> <mk-reactions-viewer post={ p } ref="reactionsViewer"/> - <button onclick={ reply }><i class="fa fa-reply"></i> - <p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> + <button onclick={ reply }> + <i class="fa fa-reply"></i><p class="count" if={ p.replies_count > 0 }>{ p.replies_count }</p> </button> - <button onclick={ repost } title="Repost"><i class="fa fa-retweet"></i> - <p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> + <button onclick={ repost } title="Repost"> + <i class="fa fa-retweet"></i><p class="count" if={ p.repost_count > 0 }>{ p.repost_count }</p> </button> - <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton"><i class="fa fa-plus"></i> - <p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + <button class={ reacted: p.my_reaction != null } onclick={ react } ref="reactButton"> + <i class="fa fa-plus"></i><p class="count" if={ p.reactions_count > 0 }>{ p.reactions_count }</p> + </button> + <button class="menu" onclick={ menu } ref="menuButton"> + <i class="fa fa-ellipsis-h"></i> </button> </footer> </div> @@ -431,7 +434,7 @@ > footer > button - margin 0 28px 0 0 + margin 0 padding 8px background transparent border none @@ -440,6 +443,9 @@ color #ddd cursor pointer + &:not(:last-child) + margin-right 28px + &:hover color #666 @@ -451,6 +457,10 @@ &.reacted color $theme-color + &.menu + @media (max-width 350px) + display none + </style> <script> import compile from '../../common/scripts/text-compiler'; @@ -558,6 +568,14 @@ compact: true }); }; + + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p, + compact: true + }); + }; </script> </mk-timeline-post> diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag index bd6bbad726..ea431dcc53 100644 --- a/src/web/app/mobile/tags/user.tag +++ b/src/web/app/mobile/tags/user.tag @@ -12,7 +12,7 @@ <div class="title"> <h1>{ user.name }</h1> <span class="username">@{ user.username }</span> - <span class="followed" if={ user.is_followed }>%i18n:mobile.tags.mk-user.is-followed%</span> + <span class="followed" if={ user.is_followed }>%i18n:mobile.tags.mk-user.follows-you%</span> </div> <div class="description">{ user.description }</div> <div class="info"> @@ -215,6 +215,7 @@ </mk-user> <mk-user-overview> + <mk-post-detail if={ user.pinned_post } post={ user.pinned_post } compact={ true }/> <section class="recent-posts"> <h2><i class="fa fa-comments-o"></i>%i18n:mobile.tags.mk-user-overview.recent-posts%</h2> <div> @@ -230,15 +231,25 @@ <section class="activity"> <h2><i class="fa fa-bar-chart"></i>%i18n:mobile.tags.mk-user-overview.activity%</h2> <div> - <mk-activity-table user={ user }/> + <mk-user-overview-activity-chart user={ user }/> </div> </section> + <section class="followers-you-know" if={ SIGNIN && I.id !== user.id }> + <h2><i class="fa fa-users"></i>%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> :scope display block max-width 600px margin 0 auto + > mk-post-detail + margin 0 0 8px 0 + > section background #eee border-radius 8px @@ -263,8 +274,16 @@ > div padding 8px + > p + display block + margin 16px + text-align center + color #cad2da + </style> <script> + this.mixin('i'); + this.user = this.opts.user; </script> </mk-user-overview> @@ -369,7 +388,7 @@ font-size 12px > div - padding 0 8px 8px 8px + padding 2px 8px 8px 8px height 60px overflow hidden white-space normal @@ -451,7 +470,7 @@ this.api('users/posts', { user_id: this.user.id, with_media: true, - limit: 9 + limit: 6 }).then(posts => { this.initializing = false; posts.forEach(post => { @@ -467,3 +486,112 @@ }); </script> </mk-user-overview-photos> + +<mk-user-overview-activity-chart> + <svg 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> + :scope + display block + max-width 600px + margin 0 auto + + > svg + display block + width 100% + height 80px + + > rect + transform-origin center + + </style> + <script> + this.mixin('api'); + + this.user = this.opts.user; + + this.on('mount', () => { + this.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-followers-you-know> + <p class="initializing" if={ initializing }><i class="fa fa-spinner fa-pulse fa-fw"></i>%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p> + <div if={ !initializing && users.length > 0 }> + <virtual each={ user in users }> + <a href={ '/' + user.username }><img src={ user.avatar_url + '?thumbnail&size=64' } alt={ user.name }/></a> + </virtual> + </div> + <p class="empty" if={ !initializing && users.length == 0 }>%i18n:mobile.tags.mk-user-overview-followers-you-know.no-users%</p> + <style> + :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> + this.mixin('api'); + + this.user = this.opts.user; + this.initializing = true; + + this.on('mount', () => { + this.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> |