mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-12-27 19:23:08 +02:00
Implement #770
This commit is contained in:
parent
5c37b9cef3
commit
13a568889c
9 changed files with 228 additions and 50 deletions
|
@ -5,6 +5,7 @@ ChangeLog (Release Notes)
|
|||
unreleased
|
||||
----------
|
||||
* New: ユーザーページによく使うドメインを表示 (#771)
|
||||
* New: よくリプライするユーザーをユーザーページに表示 (#770)
|
||||
|
||||
2566 (2017/09/07)
|
||||
-----------------
|
||||
|
|
|
@ -499,6 +499,7 @@ mobile:
|
|||
activity: "Activity"
|
||||
keywords: "Keywords"
|
||||
domains: "Domains"
|
||||
frequently-replied-users: "Frequently talking users"
|
||||
followers-you-know: "Followers you know"
|
||||
last-used-at: "Latest used at"
|
||||
|
||||
|
@ -516,6 +517,10 @@ mobile:
|
|||
mk-user-overview-domains:
|
||||
no-domains: "No domains"
|
||||
|
||||
mk-user-overview-frequently-replied-users:
|
||||
loading: "Loading"
|
||||
no-users: "No users"
|
||||
|
||||
mk-user-overview-followers-you-know:
|
||||
loading: "Loading"
|
||||
no-users: "No users"
|
||||
|
|
|
@ -499,6 +499,7 @@ mobile:
|
|||
activity: "アクティビティ"
|
||||
keywords: "キーワード"
|
||||
domains: "頻出ドメイン"
|
||||
frequently-replied-users: "よく会話するユーザー"
|
||||
followers-you-know: "知り合いのフォロワー"
|
||||
last-used-at: "最終ログイン"
|
||||
|
||||
|
@ -516,6 +517,10 @@ mobile:
|
|||
mk-user-overview-domains:
|
||||
no-domains: "よく表れるドメインは検出されませんでした"
|
||||
|
||||
mk-user-overview-frequently-replied-users:
|
||||
loading: "読み込み中"
|
||||
no-users: "よく会話するユーザーはいません"
|
||||
|
||||
mk-user-overview-followers-you-know:
|
||||
loading: "読み込み中"
|
||||
no-users: "知り合いのユーザーはいません"
|
||||
|
|
|
@ -326,6 +326,9 @@ const endpoints: Endpoint[] = [
|
|||
withCredential: true,
|
||||
kind: 'account-read'
|
||||
},
|
||||
{
|
||||
name: 'users/get_frequently_replied_users'
|
||||
},
|
||||
|
||||
{
|
||||
name: 'following/create',
|
||||
|
|
96
src/api/endpoints/users/get_frequently_replied_users.ts
Normal file
96
src/api/endpoints/users/get_frequently_replied_users.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
import $ from 'cafy';
|
||||
import Post from '../../models/post';
|
||||
import User from '../../models/user';
|
||||
import serialize from '../../serializers/user';
|
||||
|
||||
module.exports = (params, me) => new Promise(async (res, rej) => {
|
||||
// Get 'user_id' parameter
|
||||
const [userId, userIdErr] = $(params.user_id).id().$;
|
||||
if (userIdErr) return rej('invalid user_id param');
|
||||
|
||||
// Lookup user
|
||||
const user = await User.findOne({
|
||||
_id: userId
|
||||
}, {
|
||||
fields: {
|
||||
_id: true
|
||||
}
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
return rej('user not found');
|
||||
}
|
||||
|
||||
// Fetch recent posts
|
||||
const recentPosts = await Post.find({
|
||||
user_id: user._id,
|
||||
reply_to_id: {
|
||||
$exists: true,
|
||||
$ne: null
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
_id: -1
|
||||
},
|
||||
limit: 1000,
|
||||
fields: {
|
||||
_id: false,
|
||||
reply_to_id: true
|
||||
}
|
||||
});
|
||||
|
||||
// 投稿が少なかったら中断
|
||||
if (recentPosts.length === 0) {
|
||||
return res([]);
|
||||
}
|
||||
|
||||
const replyTargetPosts = await Post.find({
|
||||
_id: {
|
||||
$in: recentPosts.map(p => p.reply_to_id)
|
||||
},
|
||||
user_id: {
|
||||
$ne: user._id
|
||||
}
|
||||
}, {
|
||||
fields: {
|
||||
_id: false,
|
||||
user_id: true
|
||||
}
|
||||
});
|
||||
|
||||
const repliedUsers = {};
|
||||
|
||||
// Extract replies from recent posts
|
||||
replyTargetPosts.forEach(post => {
|
||||
const userId = post.user_id.toString();
|
||||
if (repliedUsers[userId]) {
|
||||
repliedUsers[userId]++;
|
||||
} else {
|
||||
repliedUsers[userId] = 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Calc peak
|
||||
let peak = 0;
|
||||
Object.keys(repliedUsers).forEach(user => {
|
||||
if (repliedUsers[user] > peak) peak = repliedUsers[user];
|
||||
});
|
||||
|
||||
// Sort replies by frequency
|
||||
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
|
||||
|
||||
// Lookup top 10 replies
|
||||
const topRepliedUsers = repliedUsersSorted.slice(0, 10);
|
||||
|
||||
// Make replies object (includes weights)
|
||||
const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
|
||||
user: await serialize(user, me, { detail: true }),
|
||||
weight: repliedUsers[user] / peak
|
||||
})));
|
||||
|
||||
// Response
|
||||
res(repliesObj);
|
||||
});
|
|
@ -49,3 +49,4 @@ require('./users-list.tag');
|
|||
require('./user-following.tag');
|
||||
require('./user-followers.tag');
|
||||
require('./init-following.tag');
|
||||
require('./user-card.tag');
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
<mk-init-following>
|
||||
<p class="title">気になるユーザーをフォロー:</p>
|
||||
<div class="users" if={ !fetching && users.length > 0 }>
|
||||
<div class="user" each={ users }>
|
||||
<header style={ banner_url ? 'background-image: url(' + banner_url + '?thumbnail&size=1024)' : '' }>
|
||||
<a href={ '/' + username }>
|
||||
<img src={ avatar_url + '?thumbnail&size=200' } alt="avatar"/>
|
||||
</a>
|
||||
</header>
|
||||
<a class="name" href={ '/' + username } target="_blank">{ name }</a>
|
||||
<p class="username">@{ username }</p>
|
||||
<mk-follow-button user={ this }/>
|
||||
</div>
|
||||
<virtual each={ users }>
|
||||
<mk-user-card user={ this } />
|
||||
</virtual>
|
||||
</div>
|
||||
<p class="empty" if={ !fetching && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p>
|
||||
<p class="fetching" if={ fetching }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます<mk-ellipsis/></p>
|
||||
|
@ -37,49 +30,10 @@
|
|||
padding 16px
|
||||
background #eee
|
||||
|
||||
> .user
|
||||
display inline-block
|
||||
width 200px
|
||||
text-align center
|
||||
border-radius 8px
|
||||
background #fff
|
||||
|
||||
> mk-user-card
|
||||
&:not(:last-child)
|
||||
margin-right 16px
|
||||
|
||||
> header
|
||||
display block
|
||||
height 80px
|
||||
background-color #ddd
|
||||
background-size cover
|
||||
background-position center
|
||||
border-radius 8px 8px 0 0
|
||||
|
||||
> a
|
||||
> img
|
||||
position absolute
|
||||
top 20px
|
||||
left calc(50% - 40px)
|
||||
width 80px
|
||||
height 80px
|
||||
border solid 2px #fff
|
||||
border-radius 8px
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 24px 0 0 0
|
||||
font-size 16px
|
||||
color #555
|
||||
|
||||
> .username
|
||||
margin 0
|
||||
font-size 15px
|
||||
color #ccc
|
||||
|
||||
> mk-follow-button
|
||||
display inline-block
|
||||
margin 8px 0 16px 0
|
||||
|
||||
> .empty
|
||||
margin 0
|
||||
padding 16px
|
||||
|
|
55
src/web/app/mobile/tags/user-card.tag
Normal file
55
src/web/app/mobile/tags/user-card.tag
Normal file
|
@ -0,0 +1,55 @@
|
|||
<mk-user-card>
|
||||
<header style={ user.banner_url ? 'background-image: url(' + user.banner_url + '?thumbnail&size=1024)' : '' }>
|
||||
<a href={ '/' + user.username }>
|
||||
<img src={ user.avatar_url + '?thumbnail&size=200' } alt="avatar"/>
|
||||
</a>
|
||||
</header>
|
||||
<a class="name" href={ '/' + user.username } target="_blank">{ user.name }</a>
|
||||
<p class="username">@{ user.username }</p>
|
||||
<mk-follow-button user={ user }/>
|
||||
<style>
|
||||
:scope
|
||||
display inline-block
|
||||
width 200px
|
||||
text-align center
|
||||
border-radius 8px
|
||||
background #fff
|
||||
|
||||
> header
|
||||
display block
|
||||
height 80px
|
||||
background-color #ddd
|
||||
background-size cover
|
||||
background-position center
|
||||
border-radius 8px 8px 0 0
|
||||
|
||||
> a
|
||||
> img
|
||||
position absolute
|
||||
top 20px
|
||||
left calc(50% - 40px)
|
||||
width 80px
|
||||
height 80px
|
||||
border solid 2px #fff
|
||||
border-radius 8px
|
||||
|
||||
> .name
|
||||
display block
|
||||
margin 24px 0 0 0
|
||||
font-size 16px
|
||||
color #555
|
||||
|
||||
> .username
|
||||
margin 0
|
||||
font-size 15px
|
||||
color #ccc
|
||||
|
||||
> mk-follow-button
|
||||
display inline-block
|
||||
margin 8px 0 16px 0
|
||||
|
||||
</style>
|
||||
<script>
|
||||
this.user = this.opts.user;
|
||||
</script>
|
||||
</mk-user-card>
|
|
@ -246,6 +246,12 @@
|
|||
<mk-user-overview-domains user={ user }/>
|
||||
</div>
|
||||
</section>
|
||||
<section class="frequently-replied-users">
|
||||
<h2><i class="fa fa-users"></i>%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" if={ SIGNIN && I.id !== user.id }>
|
||||
<h2><i class="fa fa-users"></i>%i18n:mobile.tags.mk-user-overview.followers-you-know%</h2>
|
||||
<div>
|
||||
|
@ -619,6 +625,58 @@
|
|||
</script>
|
||||
</mk-user-overview-domains>
|
||||
|
||||
<mk-user-overview-frequently-replied-users>
|
||||
<p class="initializing" if={ initializing }><i class="fa fa-spinner fa-pulse fa-fw"></i>%i18n:mobile.tags.mk-user-overview-frequently-replied-users.loading%<mk-ellipsis/></p>
|
||||
<div if={ !initializing && users.length > 0 }>
|
||||
<virtual each={ users }>
|
||||
<mk-user-card user={ this.user }/>
|
||||
</virtual>
|
||||
</div>
|
||||
<p class="empty" if={ !initializing && users.length == 0 }>%i18n:mobile.tags.mk-user-overview-frequently-replied-users.no-users%</p>
|
||||
<style>
|
||||
: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>
|
||||
this.mixin('api');
|
||||
|
||||
this.user = this.opts.user;
|
||||
this.initializing = true;
|
||||
|
||||
this.on('mount', () => {
|
||||
this.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" 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 }>
|
||||
|
|
Loading…
Reference in a new issue