Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
2fcc3bb1ea | |||
2e680c3d1e | |||
af0a0ef41b | |||
bbfccb0bbf | |||
c89eb5d69f | |||
ebde84214e | |||
03fbae7b6d | |||
f90e9596d4 | |||
944f9524e2 | |||
c61050244e | |||
90337adbbc | |||
7b67e41c5b |
@ -70,7 +70,7 @@ common:
|
|||||||
donation: "Dotacje"
|
donation: "Dotacje"
|
||||||
nav: "Nawigacja"
|
nav: "Nawigacja"
|
||||||
tips: "Wskazówki"
|
tips: "Wskazówki"
|
||||||
hashtags: "ハッシュタグ"
|
hashtags: "Hashtagi"
|
||||||
deck:
|
deck:
|
||||||
widgets: "Widżety"
|
widgets: "Widżety"
|
||||||
home: "Strona główna"
|
home: "Strona główna"
|
||||||
@ -226,8 +226,8 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
title: "Wykres wpisów"
|
title: "Wykres wpisów"
|
||||||
toggle: "Przełącz widok"
|
toggle: "Przełącz widok"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "Hashtagi"
|
||||||
count: "{}人が投稿"
|
count: "Wspomniany przez {} użytkowników"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Informacje o serwerze"
|
title: "Informacje o serwerze"
|
||||||
toggle: "Przełącz widok"
|
toggle: "Przełącz widok"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "2.37.3",
|
"version": "2.37.4",
|
||||||
"clientVersion": "1.0.6453",
|
"clientVersion": "1.0.6465",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -49,7 +49,8 @@ export default Vue.extend({
|
|||||||
polylinePoints: '',
|
polylinePoints: '',
|
||||||
polygonPoints: '',
|
polygonPoints: '',
|
||||||
headX: null,
|
headX: null,
|
||||||
headY: null
|
headY: null,
|
||||||
|
clock: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -59,6 +60,12 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.draw();
|
this.draw();
|
||||||
|
|
||||||
|
// Vueが何故かWatchを発動させない場合があるので
|
||||||
|
this.clock = setInterval(this.draw, 1000);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.clock);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
draw() {
|
draw() {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
|
<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
|
||||||
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
|
||||||
<div v-else>
|
<transition-group v-else tag="div" name="chart">
|
||||||
<div v-for="stat in stats" :key="stat.tag">
|
<div v-for="stat in stats" :key="stat.tag">
|
||||||
<div class="tag">
|
<div class="tag">
|
||||||
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
|
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
|
||||||
@ -13,7 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<x-chart class="chart" :src="stat.chart"/>
|
<x-chart class="chart" :src="stat.chart"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</mk-widget-container>
|
</mk-widget-container>
|
||||||
</div>
|
</div>
|
||||||
@ -74,6 +74,14 @@ root(isDark)
|
|||||||
margin-right 4px
|
margin-right 4px
|
||||||
|
|
||||||
> div
|
> div
|
||||||
|
.chart-enter
|
||||||
|
.chart-leave-to
|
||||||
|
opacity 0
|
||||||
|
transform translateY(-30px)
|
||||||
|
|
||||||
|
> *
|
||||||
|
transition transform .3s ease, opacity .3s ease
|
||||||
|
|
||||||
> div
|
> div
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
import Note from '../../../../models/note';
|
import Note from '../../../../models/note';
|
||||||
|
|
||||||
|
/*
|
||||||
|
トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
|
||||||
|
ユニーク投稿数とはそのハッシュタグと投稿ユーザーのペアのカウントで、例えば同じユーザーが複数回同じハッシュタグを投稿してもそのハッシュタグのユニーク投稿数は1とカウントされる
|
||||||
|
*/
|
||||||
|
|
||||||
|
const rangeA = 1000 * 60 * 30; // 30分
|
||||||
|
const rangeB = 1000 * 60 * 120; // 2時間
|
||||||
|
const coefficient = 1.5; // 「n倍」の部分
|
||||||
|
const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get trends of hashtags
|
* Get trends of hashtags
|
||||||
*/
|
*/
|
||||||
module.exports = () => new Promise(async (res, rej) => {
|
module.exports = () => new Promise(async (res, rej) => {
|
||||||
|
//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
|
||||||
const data = await Note.aggregate([{
|
const data = await Note.aggregate([{
|
||||||
$match: {
|
$match: {
|
||||||
createdAt: {
|
createdAt: {
|
||||||
$gt: new Date(Date.now() - 1000 * 60 * 60)
|
$gt: new Date(Date.now() - rangeA)
|
||||||
},
|
},
|
||||||
tags: {
|
tags: {
|
||||||
$exists: true,
|
$exists: true,
|
||||||
@ -26,13 +37,15 @@ module.exports = () => new Promise(async (res, rej) => {
|
|||||||
userId: any;
|
userId: any;
|
||||||
}
|
}
|
||||||
}>;
|
}>;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
return res([]);
|
return res([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = [];
|
let tags = [];
|
||||||
|
|
||||||
|
// カウント
|
||||||
data.map(x => x._id).forEach(x => {
|
data.map(x => x._id).forEach(x => {
|
||||||
const i = tags.findIndex(tag => tag.name == x.tags);
|
const i = tags.findIndex(tag => tag.name == x.tags);
|
||||||
if (i != -1) {
|
if (i != -1) {
|
||||||
@ -45,11 +58,34 @@ module.exports = () => new Promise(async (res, rej) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const hots = tags
|
// 最低要求投稿者数を下回るならカットする
|
||||||
|
tags = tags.filter(tag => tag.count >= requiredUsers);
|
||||||
|
|
||||||
|
//#region 2. 1で取得したそれぞれのタグについて、「直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上」かどうかを判定する
|
||||||
|
const hotsPromises = tags.map(async tag => {
|
||||||
|
const passedCount = (await Note.distinct('userId', {
|
||||||
|
tags: tag.name,
|
||||||
|
createdAt: {
|
||||||
|
$lt: new Date(Date.now() - rangeA),
|
||||||
|
$gt: new Date(Date.now() - rangeB)
|
||||||
|
}
|
||||||
|
}) as any).length;
|
||||||
|
|
||||||
|
if (tag.count > (passedCount * coefficient)) {
|
||||||
|
return tag;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
const hots = (await Promise.all(hotsPromises))
|
||||||
|
.filter(x => x != null)
|
||||||
.sort((a, b) => b.count - a.count)
|
.sort((a, b) => b.count - a.count)
|
||||||
.map(tag => tag.name)
|
.map(tag => tag.name)
|
||||||
.slice(0, 5);
|
.slice(0, 5);
|
||||||
|
|
||||||
|
//#region 2で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
|
||||||
const countPromises: Array<Promise<any[]>> = [];
|
const countPromises: Array<Promise<any[]>> = [];
|
||||||
|
|
||||||
const range = 20;
|
const range = 20;
|
||||||
@ -75,6 +111,7 @@ module.exports = () => new Promise(async (res, rej) => {
|
|||||||
$gt: new Date(Date.now() - (interval * range))
|
$gt: new Date(Date.now() - (interval * range))
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
|
//#endregion
|
||||||
|
|
||||||
const stats = hots.map((tag, i) => ({
|
const stats = hots.map((tag, i) => ({
|
||||||
tag,
|
tag,
|
||||||
|
Reference in New Issue
Block a user