Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
f968d05ea0 | |||
d6e5dc2167 | |||
460147fea2 | |||
cea44834bb | |||
1af50fd7b8 | |||
b18013025f | |||
399eb60809 | |||
ed67e3506b | |||
d8ff37fc45 | |||
2fcc3bb1ea | |||
2e680c3d1e | |||
af0a0ef41b | |||
bbfccb0bbf | |||
c89eb5d69f | |||
ebde84214e | |||
03fbae7b6d | |||
f90e9596d4 | |||
944f9524e2 | |||
c61050244e | |||
90337adbbc | |||
7b67e41c5b | |||
91db24fcfc | |||
bb53db905f | |||
0e9a1efe46 | |||
289cd3e200 | |||
e0f847e539 | |||
c2842b486e | |||
7235ade42f | |||
850be2df1d | |||
d504501440 | |||
208392f12c | |||
0fe036c640 | |||
a40c41f0b0 | |||
4affa5b710 | |||
4eb574d991 | |||
2c1577ea24 | |||
b87e7e50b6 | |||
36215d50bd | |||
5ff1245d0c | |||
ebd189fb27 | |||
6f724827bd | |||
b6a0982012 | |||
c3e375e8a5 | |||
302409fd83 | |||
a2046461c1 | |||
6660c34120 | |||
b88ccf0ddd | |||
b898bbf94c | |||
787e89eb95 | |||
1022c2c438 | |||
ba21c62ed4 | |||
bfe66c919b | |||
3dacf7f661 |
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "Spenden"
|
donation: "Spenden"
|
||||||
nav: "Navigation"
|
nav: "Navigation"
|
||||||
tips: "Tipps"
|
tips: "Tipps"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "Widget hinzufügen:"
|
widgets: "Widget hinzufügen:"
|
||||||
home: "Startseite"
|
home: "Startseite"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Serverinformationen"
|
title: "Serverinformationen"
|
||||||
toggle: "Sicht umschalten"
|
toggle: "Sicht umschalten"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "Donation"
|
donation: "Donation"
|
||||||
nav: "Navigation"
|
nav: "Navigation"
|
||||||
tips: "Tips"
|
tips: "Tips"
|
||||||
|
hashtags: "Hashtags"
|
||||||
deck:
|
deck:
|
||||||
widgets: "Widgets"
|
widgets: "Widgets"
|
||||||
home: "Home"
|
home: "Home"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "Toggle views"
|
toggle: "Toggle views"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "Hashtags"
|
title: "Hashtags"
|
||||||
|
count: "{} users mentioned"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Server info"
|
title: "Server info"
|
||||||
toggle: "Toggle views"
|
toggle: "Toggle views"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "Dons"
|
donation: "Dons"
|
||||||
nav: "Navigation"
|
nav: "Navigation"
|
||||||
tips: "Conseils"
|
tips: "Conseils"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "Widgets"
|
widgets: "Widgets"
|
||||||
home: "Accueil"
|
home: "Accueil"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "Info sur le serveur"
|
title: "Info sur le serveur"
|
||||||
toggle: "Afficher les vues"
|
toggle: "Afficher les vues"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -257,6 +257,8 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
|
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
|
empty: "トレンドなし"
|
||||||
|
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "Dotacje"
|
donation: "Dotacje"
|
||||||
nav: "Nawigacja"
|
nav: "Nawigacja"
|
||||||
tips: "Wskazówki"
|
tips: "Wskazówki"
|
||||||
|
hashtags: "Hashtagi"
|
||||||
deck:
|
deck:
|
||||||
widgets: "Widżety"
|
widgets: "Widżety"
|
||||||
home: "Strona główna"
|
home: "Strona główna"
|
||||||
@ -225,7 +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: "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"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -70,6 +70,7 @@ common:
|
|||||||
donation: "寄付のお願い"
|
donation: "寄付のお願い"
|
||||||
nav: "ナビゲーション"
|
nav: "ナビゲーション"
|
||||||
tips: "ヒント"
|
tips: "ヒント"
|
||||||
|
hashtags: "ハッシュタグ"
|
||||||
deck:
|
deck:
|
||||||
widgets: "ウィジェット"
|
widgets: "ウィジェット"
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
@ -226,6 +227,7 @@ common/views/widgets/posts-monitor.vue:
|
|||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
common/views/widgets/hashtags.vue:
|
common/views/widgets/hashtags.vue:
|
||||||
title: "ハッシュタグ"
|
title: "ハッシュタグ"
|
||||||
|
count: "{}人が投稿"
|
||||||
common/views/widgets/server.vue:
|
common/views/widgets/server.vue:
|
||||||
title: "サーバー情報"
|
title: "サーバー情報"
|
||||||
toggle: "表示を切り替え"
|
toggle: "表示を切り替え"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "2.37.1",
|
"version": "2.37.7",
|
||||||
"clientVersion": "1.0.6419",
|
"clientVersion": "1.0.6474",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -40,6 +40,17 @@ export default Vue.component('mk-note-html', {
|
|||||||
ast = this.ast;
|
ast = this.ast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ast.filter(x => x.type != 'hashtag').length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (
|
||||||
|
ast[ast.length - 1].type == 'hashtag' ||
|
||||||
|
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == ' ') ||
|
||||||
|
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == '\n')) {
|
||||||
|
ast.pop();
|
||||||
|
}
|
||||||
|
|
||||||
// Parse ast to DOM
|
// Parse ast to DOM
|
||||||
const els = flatten(ast.map(token => {
|
const els = flatten(ast.map(token => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
@ -92,7 +103,7 @@ export default Vue.component('mk-note-html', {
|
|||||||
case 'hashtag':
|
case 'hashtag':
|
||||||
return createElement('a', {
|
return createElement('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
href: `${url}/search?q=${token.content}`,
|
href: `${url}/tags/${token.content}`,
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
}, token.content);
|
}, token.content);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
|
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" style="overflow:visible">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient :id="gradientId" x1="0" x2="0" y1="1" y2="0">
|
<linearGradient :id="gradientId" x1="0" x2="0" y1="1" y2="0">
|
||||||
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
|
<stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
|
||||||
@ -14,17 +14,17 @@
|
|||||||
:points="polylinePoints"
|
:points="polylinePoints"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#fff"
|
stroke="#fff"
|
||||||
stroke-width="0.7"/>
|
stroke-width="2"/>
|
||||||
<circle
|
<circle
|
||||||
:cx="headX"
|
:cx="headX"
|
||||||
:cy="headY"
|
:cy="headY"
|
||||||
r="1.2"
|
r="3"
|
||||||
fill="#fff"/>
|
fill="#fff"/>
|
||||||
</mask>
|
</mask>
|
||||||
</defs>
|
</defs>
|
||||||
<rect
|
<rect
|
||||||
x="-2" y="-2"
|
x="-10" y="-10"
|
||||||
:width="viewBoxX + 4" :height="viewBoxY + 4"
|
:width="viewBoxX + 20" :height="viewBoxY + 20"
|
||||||
:style="`stroke: none; fill: url(#${ gradientId }); mask: url(#${ maskId })`"/>
|
:style="`stroke: none; fill: url(#${ gradientId }); mask: url(#${ maskId })`"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
@ -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,16 +60,26 @@ 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() {
|
||||||
const stats = this.src.slice().reverse();
|
const stats = this.src.slice().reverse();
|
||||||
const peak = Math.max.apply(null, stats) || 1;
|
const peak = Math.max.apply(null, stats) || 1;
|
||||||
|
|
||||||
const polylinePoints = stats.map((x, i) => [this.viewBoxX - ((stats.length - 1) - i), (1 - (x / peak)) * this.viewBoxY]);
|
const polylinePoints = stats.map((n, i) => [
|
||||||
|
i * (this.viewBoxX / (stats.length - 1)),
|
||||||
|
(1 - (n / peak)) * this.viewBoxY
|
||||||
|
]);
|
||||||
|
|
||||||
this.polylinePoints = polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
this.polylinePoints = polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
|
||||||
|
|
||||||
this.polygonPoints = `${this.viewBoxX - (stats.length - 1)},${ this.viewBoxY } ${ this.polylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
this.polygonPoints = `0,${ this.viewBoxY } ${ this.polylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
|
||||||
|
|
||||||
this.headX = polylinePoints[polylinePoints.length - 1][0];
|
this.headX = polylinePoints[polylinePoints.length - 1][0];
|
||||||
this.headY = polylinePoints[polylinePoints.length - 1][1];
|
this.headY = polylinePoints[polylinePoints.length - 1][1];
|
||||||
|
@ -5,14 +5,16 @@
|
|||||||
|
|
||||||
<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>
|
<p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
|
||||||
|
<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 }`">#{{ stat.tag }}</router-link>
|
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
|
||||||
|
<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
|
||||||
</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>
|
||||||
@ -40,7 +42,7 @@ export default define({
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
this.clock = setInterval(this.fetch, 1000 * 60 * 10);
|
this.clock = setInterval(this.fetch, 1000 * 60);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
clearInterval(this.clock);
|
clearInterval(this.clock);
|
||||||
@ -64,6 +66,7 @@ export default define({
|
|||||||
root(isDark)
|
root(isDark)
|
||||||
.mkw-hashtags--body
|
.mkw-hashtags--body
|
||||||
> .fetching
|
> .fetching
|
||||||
|
> .empty
|
||||||
margin 0
|
margin 0
|
||||||
padding 16px
|
padding 16px
|
||||||
text-align center
|
text-align center
|
||||||
@ -73,22 +76,42 @@ 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
|
||||||
padding 16px
|
padding 14px 16px
|
||||||
|
|
||||||
&:not(:last-child)
|
&:not(:last-child)
|
||||||
border-bottom solid 1px #393f4f
|
border-bottom solid 1px isDark ? #393f4f : #eee
|
||||||
|
|
||||||
> .tag
|
> .tag
|
||||||
flex 1
|
flex 1
|
||||||
|
overflow hidden
|
||||||
|
font-size 14px
|
||||||
|
color isDark ? #9baec8 : #65727b
|
||||||
|
|
||||||
> a
|
> a
|
||||||
color #9baec8
|
display block
|
||||||
|
width 100%
|
||||||
|
white-space nowrap
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
color inherit
|
||||||
|
|
||||||
|
> p
|
||||||
|
margin 0
|
||||||
|
font-size 75%
|
||||||
|
opacity 0.7
|
||||||
|
|
||||||
> .chart
|
> .chart
|
||||||
width 50px
|
|
||||||
height 30px
|
height 30px
|
||||||
|
|
||||||
.mkw-hashtags[data-darkmode]
|
.mkw-hashtags[data-darkmode]
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
|
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
|
||||||
<template v-if="edit">
|
<template v-if="edit">
|
||||||
<header>
|
<header>
|
||||||
<select v-model="widgetAdderSelected">
|
<select v-model="widgetAdderSelected" @change="addWidget">
|
||||||
<option value="profile">%i18n:common.widgets.profile%</option>
|
<option value="profile">%i18n:common.widgets.profile%</option>
|
||||||
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
|
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
|
||||||
<option value="calendar">%i18n:common.widgets.calendar%</option>
|
<option value="calendar">%i18n:common.widgets.calendar%</option>
|
||||||
@ -30,20 +30,15 @@
|
|||||||
<option value="nav">%i18n:common.widgets.nav%</option>
|
<option value="nav">%i18n:common.widgets.nav%</option>
|
||||||
<option value="tips">%i18n:common.widgets.tips%</option>
|
<option value="tips">%i18n:common.widgets.tips%</option>
|
||||||
</select>
|
</select>
|
||||||
<button @click="addWidget">%i18n:@add%</button>
|
|
||||||
</header>
|
</header>
|
||||||
<x-draggable
|
<x-draggable
|
||||||
:list="column.widgets"
|
:list="column.widgets"
|
||||||
:options="{ handle: '.handle', animation: 150 }"
|
:options="{ animation: 150 }"
|
||||||
@sort="onWidgetSort"
|
@sort="onWidgetSort"
|
||||||
>
|
>
|
||||||
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id">
|
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="widgetFunc(widget.id)">
|
||||||
<header>
|
<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
|
||||||
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
|
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
|
||||||
</header>
|
|
||||||
<div @click="widgetFunc(widget.id)">
|
|
||||||
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</x-draggable>
|
</x-draggable>
|
||||||
</template>
|
</template>
|
||||||
@ -142,6 +137,13 @@ export default Vue.extend({
|
|||||||
|
|
||||||
root(isDark)
|
root(isDark)
|
||||||
.gqpwvtwtprsbmnssnbicggtwqhmylhnq
|
.gqpwvtwtprsbmnssnbicggtwqhmylhnq
|
||||||
|
> header
|
||||||
|
padding 16px
|
||||||
|
|
||||||
|
> *
|
||||||
|
width 100%
|
||||||
|
padding 4px
|
||||||
|
|
||||||
.widget, .customize-container
|
.widget, .customize-container
|
||||||
margin 8px
|
margin 8px
|
||||||
|
|
||||||
@ -149,7 +151,21 @@ root(isDark)
|
|||||||
margin-top 0
|
margin-top 0
|
||||||
|
|
||||||
.customize-container
|
.customize-container
|
||||||
background #fff
|
cursor move
|
||||||
|
|
||||||
|
> *:not(.remove)
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
> .remove
|
||||||
|
position absolute
|
||||||
|
z-index 1
|
||||||
|
top 8px
|
||||||
|
right 8px
|
||||||
|
width 32px
|
||||||
|
height 32px
|
||||||
|
color #fff
|
||||||
|
background rgba(#000, 0.7)
|
||||||
|
border-radius 4px
|
||||||
|
|
||||||
> header
|
> header
|
||||||
color isDark ? #fff : #000
|
color isDark ? #fff : #000
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
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; // 最低何人がそのタグを投稿している必要があるか
|
||||||
|
|
||||||
|
const max = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get trends of hashtags
|
* Get trends of hashtags
|
||||||
*/
|
*/
|
||||||
module.exports = (params, user) => 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,
|
||||||
@ -18,49 +31,83 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||||||
$unwind: '$tags'
|
$unwind: '$tags'
|
||||||
}, {
|
}, {
|
||||||
$group: {
|
$group: {
|
||||||
_id: '$tags',
|
_id: { tags: '$tags', userId: '$userId' }
|
||||||
count: {
|
|
||||||
$sum: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
$group: {
|
|
||||||
_id: null,
|
|
||||||
tags: {
|
|
||||||
$push: {
|
|
||||||
tag: '$_id',
|
|
||||||
count: '$count'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
$project: {
|
|
||||||
_id: false,
|
|
||||||
tags: true
|
|
||||||
}
|
}
|
||||||
}]) as Array<{
|
}]) as Array<{
|
||||||
tags: Array<{
|
_id: {
|
||||||
tag: string;
|
tags: string;
|
||||||
count: number;
|
userId: any;
|
||||||
}>
|
}
|
||||||
}>;
|
}>;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
if (data.length == 0) {
|
if (data.length == 0) {
|
||||||
return res([]);
|
return res([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hots = data[0].tags
|
const tags = [];
|
||||||
|
|
||||||
|
// カウント
|
||||||
|
data.map(x => x._id).forEach(x => {
|
||||||
|
const i = tags.findIndex(tag => tag.name == x.tags);
|
||||||
|
if (i != -1) {
|
||||||
|
tags[i].count++;
|
||||||
|
} else {
|
||||||
|
tags.push({
|
||||||
|
name: x.tags,
|
||||||
|
count: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 最低要求投稿者数を下回るならカットする
|
||||||
|
const limitedTags = tags.filter(tag => tag.count >= requiredUsers);
|
||||||
|
|
||||||
|
//#region 2. 1で取得したそれぞれのタグについて、「直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上」かどうかを判定する
|
||||||
|
const hotsPromises = limitedTags.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
|
||||||
|
|
||||||
|
// タグを人気順に並べ替え
|
||||||
|
let 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.tag)
|
.map(tag => tag.name)
|
||||||
.slice(0, 10);
|
.slice(0, max);
|
||||||
|
|
||||||
const countPromises: Array<Promise<number[]>> = [];
|
//#region 3. もし上記の方法でのトレンド抽出の結果、求められているタグ数に達しなければ「ただ単に現在投稿数が多いハッシュタグ」に切り替える
|
||||||
|
if (hots.length < max) {
|
||||||
|
hots = hots.concat(tags
|
||||||
|
.filter(tag => hots.indexOf(tag.name) == -1)
|
||||||
|
.sort((a, b) => b.count - a.count)
|
||||||
|
.map(tag => tag.name)
|
||||||
|
.slice(0, max - hots.length));
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
//#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
|
||||||
// 10分
|
const countPromises: Array<Promise<any[]>> = [];
|
||||||
const interval = 1000 * 60 * 10;
|
|
||||||
|
|
||||||
countPromises.push(Promise.all(hots.map(tag => Note.count({
|
const range = 20;
|
||||||
|
|
||||||
|
// 10分
|
||||||
|
const interval = 1000 * 60 * 10;
|
||||||
|
|
||||||
|
for (let i = 0; i < range; i++) {
|
||||||
|
countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', {
|
||||||
tags: tag,
|
tags: tag,
|
||||||
createdAt: {
|
createdAt: {
|
||||||
$lt: new Date(Date.now() - (interval * i)),
|
$lt: new Date(Date.now() - (interval * i)),
|
||||||
@ -71,9 +118,18 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
|||||||
|
|
||||||
const countsLog = await Promise.all(countPromises);
|
const countsLog = await Promise.all(countPromises);
|
||||||
|
|
||||||
|
const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', {
|
||||||
|
tags: tag,
|
||||||
|
createdAt: {
|
||||||
|
$gt: new Date(Date.now() - (interval * range))
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
//#endregion
|
||||||
|
|
||||||
const stats = hots.map((tag, i) => ({
|
const stats = hots.map((tag, i) => ({
|
||||||
tag,
|
tag,
|
||||||
chart: countsLog.map(counts => counts[i])
|
chart: countsLog.map(counts => counts[i].length),
|
||||||
|
usersCount: totalCounts[i].length
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res(stats);
|
res(stats);
|
||||||
|
Reference in New Issue
Block a user