summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--locales/ja.yml3
-rw-r--r--src/client/app/common/views/widgets/hashtags.chart.vue78
-rw-r--r--src/client/app/common/views/widgets/hashtags.vue57
-rw-r--r--src/server/api/endpoints/hashtags/trend.ts12
4 files changed, 119 insertions, 31 deletions
diff --git a/locales/ja.yml b/locales/ja.yml
index 64ee00dba5..da751c9e39 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -254,6 +254,9 @@ common/views/widgets/posts-monitor.vue:
title: "投稿チャート"
toggle: "表示を切り替え"
+common/views/widgets/hashtags.vue:
+ title: "ハッシュタグ"
+
common/views/widgets/server.vue:
title: "サーバー情報"
toggle: "表示を切り替え"
diff --git a/src/client/app/common/views/widgets/hashtags.chart.vue b/src/client/app/common/views/widgets/hashtags.chart.vue
new file mode 100644
index 0000000000..19b56ef283
--- /dev/null
+++ b/src/client/app/common/views/widgets/hashtags.chart.vue
@@ -0,0 +1,78 @@
+<template>
+<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
+ <defs>
+ <linearGradient :id="gradientId" x1="0" x2="0" y1="1" y2="0">
+ <stop offset="0%" stop-color="hsl(200, 80%, 70%)"></stop>
+ <stop offset="100%" stop-color="hsl(90, 80%, 70%)"></stop>
+ </linearGradient>
+ <mask :id="maskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY">
+ <polygon
+ :points="polygonPoints"
+ fill="#fff"
+ fill-opacity="0.5"/>
+ <polyline
+ :points="polylinePoints"
+ fill="none"
+ stroke="#fff"
+ stroke-width="0.7"/>
+ <circle
+ :cx="headX"
+ :cy="headY"
+ r="1.2"
+ fill="#fff"/>
+ </mask>
+ </defs>
+ <rect
+ x="-2" y="-2"
+ :width="viewBoxX + 4" :height="viewBoxY + 4"
+ :style="`stroke: none; fill: url(#${ gradientId }); mask: url(#${ maskId })`"/>
+</svg>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import * as uuid from 'uuid';
+
+export default Vue.extend({
+ props: {
+ src: {
+ type: Array,
+ required: true
+ }
+ },
+ data() {
+ return {
+ viewBoxX: 50,
+ viewBoxY: 30,
+ gradientId: uuid(),
+ maskId: uuid(),
+ polylinePoints: '',
+ polygonPoints: '',
+ headX: null,
+ headY: null
+ };
+ },
+ watch: {
+ src() {
+ this.draw();
+ }
+ },
+ created() {
+ this.draw();
+ },
+ methods: {
+ draw() {
+ const stats = this.src.slice().reverse();
+ 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]);
+ 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.headX = polylinePoints[polylinePoints.length - 1][0];
+ this.headY = polylinePoints[polylinePoints.length - 1][1];
+ }
+ }
+});
+</script>
diff --git a/src/client/app/common/views/widgets/hashtags.vue b/src/client/app/common/views/widgets/hashtags.vue
index 0ac62af705..c4647ee0f9 100644
--- a/src/client/app/common/views/widgets/hashtags.vue
+++ b/src/client/app/common/views/widgets/hashtags.vue
@@ -6,7 +6,12 @@
<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>
<div v-else>
- <router-link v-for="stat in stats" :key="stat.tag" :to="`/tags/${ stat.tag }`">{{ stat.tag }}</router-link>
+ <div v-for="stat in stats" :key="stat.tag">
+ <div class="tag">
+ <router-link :to="`/tags/${ stat.tag }`">#{{ stat.tag }}</router-link>
+ </div>
+ <x-chart class="chart" :src="stat.chart"/>
+ </div>
</div>
</div>
</mk-widget-container>
@@ -15,12 +20,17 @@
<script lang="ts">
import define from '../../../common/define-widget';
+import XChart from './hashtags.chart.vue';
+
export default define({
name: 'hashtags',
props: () => ({
compact: false
})
}).extend({
+ components: {
+ XChart
+ },
data() {
return {
stats: [],
@@ -52,21 +62,8 @@ export default define({
<style lang="stylus" scoped>
root(isDark)
- .mkw-rss--body
- .feed
- padding 12px 16px
- font-size 0.9em
-
- > a
- display block
- padding 4px 0
- color isDark ? #9aa4b3 : #666
- border-bottom dashed 1px isDark ? #1c2023 : #eee
-
- &:last-child
- border-bottom none
-
- .fetching
+ .mkw-hashtags--body
+ > .fetching
margin 0
padding 16px
text-align center
@@ -75,23 +72,29 @@ root(isDark)
> [data-fa]
margin-right 4px
- &[data-mobile]
- background isDark ? #21242f : #f3f3f3
+ > div
+ > div
+ display flex
+ align-items center
+ padding 16px
+
+ &:not(:last-child)
+ border-bottom solid 1px #393f4f
- .feed
- padding 0
+ > .tag
+ flex 1
- > a
- padding 8px 16px
- border-bottom none
+ > a
+ color #9baec8
- &:nth-child(even)
- background isDark ? rgba(#000, 0.05) : rgba(#fff, 0.7)
+ > .chart
+ width 50px
+ height 30px
-.mkw-rss[data-darkmode]
+.mkw-hashtags[data-darkmode]
root(true)
-.mkw-rss:not([data-darkmode])
+.mkw-hashtags:not([data-darkmode])
root(false)
</style>
diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts
index 3b95e1fa4c..6a49fec3d8 100644
--- a/src/server/api/endpoints/hashtags/trend.ts
+++ b/src/server/api/endpoints/hashtags/trend.ts
@@ -4,13 +4,10 @@ import Note from '../../../../models/note';
* Get trends of hashtags
*/
module.exports = (params, user) => new Promise(async (res, rej) => {
- // 10分
- const interval = 1000 * 60 * 10;
-
const data = await Note.aggregate([{
$match: {
createdAt: {
- $gt: new Date(Date.now() - interval)
+ $gt: new Date(Date.now() - 1000 * 60 * 60)
},
tags: {
$exists: true,
@@ -48,6 +45,10 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
}>
}>;
+ if (data.length == 0) {
+ return res([]);
+ }
+
const hots = data[0].tags
.sort((a, b) => a.count - b.count)
.map(tag => tag.tag)
@@ -56,6 +57,9 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
const countPromises: Array<Promise<number[]>> = [];
for (let i = 0; i < 10; i++) {
+ // 10分
+ const interval = 1000 * 60 * 10;
+
countPromises.push(Promise.all(hots.map(tag => Note.count({
tags: tag,
createdAt: {