summaryrefslogtreecommitdiff
path: root/src/web/app
diff options
context:
space:
mode:
Diffstat (limited to 'src/web/app')
-rw-r--r--src/web/app/common/scripts/home-stream.js5
-rw-r--r--src/web/app/common/tags/index.js1
-rw-r--r--src/web/app/common/tags/post-menu.tag134
-rw-r--r--src/web/app/desktop/tags/post-detail.tag23
-rw-r--r--src/web/app/desktop/tags/timeline.tag21
-rw-r--r--src/web/app/mobile/tags/init-following.tag2
-rw-r--r--src/web/app/mobile/tags/page/post.tag14
-rw-r--r--src/web/app/mobile/tags/post-detail.tag59
-rw-r--r--src/web/app/mobile/tags/timeline.tag32
-rw-r--r--src/web/app/mobile/tags/user.tag136
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>