mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-05 11:24:44 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f968d05ea0 | ||
|
|
d6e5dc2167 | ||
|
|
460147fea2 | ||
|
|
cea44834bb | ||
|
|
1af50fd7b8 | ||
|
|
b18013025f | ||
|
|
399eb60809 | ||
|
|
ed67e3506b | ||
|
|
d8ff37fc45 |
@@ -258,6 +258,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "2.37.4",
|
||||
"clientVersion": "1.0.6465",
|
||||
"version": "2.37.7",
|
||||
"clientVersion": "1.0.6474",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
|
||||
@@ -40,6 +40,17 @@ export default Vue.component('mk-note-html', {
|
||||
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
|
||||
const els = flatten(ast.map(token => {
|
||||
switch (token.type) {
|
||||
@@ -92,7 +103,7 @@ export default Vue.component('mk-note-html', {
|
||||
case 'hashtag':
|
||||
return createElement('a', {
|
||||
attrs: {
|
||||
href: `${url}/search?q=${token.content}`,
|
||||
href: `${url}/tags/${token.content}`,
|
||||
target: '_blank'
|
||||
}
|
||||
}, token.content);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
<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="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 class="tag">
|
||||
@@ -65,8 +66,9 @@ export default define({
|
||||
root(isDark)
|
||||
.mkw-hashtags--body
|
||||
> .fetching
|
||||
> .empty
|
||||
margin 0
|
||||
padding 12px 16px
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
|
||||
@@ -85,7 +87,7 @@ root(isDark)
|
||||
> div
|
||||
display flex
|
||||
align-items center
|
||||
padding 16px
|
||||
padding 14px 16px
|
||||
|
||||
&:not(:last-child)
|
||||
border-bottom solid 1px isDark ? #393f4f : #eee
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
|
||||
<template v-if="edit">
|
||||
<header>
|
||||
<select v-model="widgetAdderSelected">
|
||||
<select v-model="widgetAdderSelected" @change="addWidget">
|
||||
<option value="profile">%i18n:common.widgets.profile%</option>
|
||||
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
|
||||
<option value="calendar">%i18n:common.widgets.calendar%</option>
|
||||
@@ -30,20 +30,15 @@
|
||||
<option value="nav">%i18n:common.widgets.nav%</option>
|
||||
<option value="tips">%i18n:common.widgets.tips%</option>
|
||||
</select>
|
||||
<button @click="addWidget">%i18n:@add%</button>
|
||||
</header>
|
||||
<x-draggable
|
||||
:list="column.widgets"
|
||||
:options="{ handle: '.handle', animation: 150 }"
|
||||
:options="{ animation: 150 }"
|
||||
@sort="onWidgetSort"
|
||||
>
|
||||
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id">
|
||||
<header>
|
||||
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
|
||||
</header>
|
||||
<div @click="widgetFunc(widget.id)">
|
||||
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
|
||||
</div>
|
||||
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="widgetFunc(widget.id)">
|
||||
<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"/>
|
||||
</div>
|
||||
</x-draggable>
|
||||
</template>
|
||||
@@ -142,6 +137,13 @@ export default Vue.extend({
|
||||
|
||||
root(isDark)
|
||||
.gqpwvtwtprsbmnssnbicggtwqhmylhnq
|
||||
> header
|
||||
padding 16px
|
||||
|
||||
> *
|
||||
width 100%
|
||||
padding 4px
|
||||
|
||||
.widget, .customize-container
|
||||
margin 8px
|
||||
|
||||
@@ -149,7 +151,21 @@ root(isDark)
|
||||
margin-top 0
|
||||
|
||||
.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
|
||||
color isDark ? #fff : #000
|
||||
|
||||
@@ -10,6 +10,8 @@ const rangeB = 1000 * 60 * 120; // 2時間
|
||||
const coefficient = 1.5; // 「n倍」の部分
|
||||
const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか
|
||||
|
||||
const max = 5;
|
||||
|
||||
/**
|
||||
* Get trends of hashtags
|
||||
*/
|
||||
@@ -43,7 +45,7 @@ module.exports = () => new Promise(async (res, rej) => {
|
||||
return res([]);
|
||||
}
|
||||
|
||||
let tags = [];
|
||||
const tags = [];
|
||||
|
||||
// カウント
|
||||
data.map(x => x._id).forEach(x => {
|
||||
@@ -59,10 +61,10 @@ module.exports = () => new Promise(async (res, rej) => {
|
||||
});
|
||||
|
||||
// 最低要求投稿者数を下回るならカットする
|
||||
tags = tags.filter(tag => tag.count >= requiredUsers);
|
||||
const limitedTags = tags.filter(tag => tag.count >= requiredUsers);
|
||||
|
||||
//#region 2. 1で取得したそれぞれのタグについて、「直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上」かどうかを判定する
|
||||
const hotsPromises = tags.map(async tag => {
|
||||
const hotsPromises = limitedTags.map(async tag => {
|
||||
const passedCount = (await Note.distinct('userId', {
|
||||
tags: tag.name,
|
||||
createdAt: {
|
||||
@@ -71,7 +73,7 @@ module.exports = () => new Promise(async (res, rej) => {
|
||||
}
|
||||
}) as any).length;
|
||||
|
||||
if (tag.count > (passedCount * coefficient)) {
|
||||
if (tag.count >= (passedCount * coefficient)) {
|
||||
return tag;
|
||||
} else {
|
||||
return null;
|
||||
@@ -79,13 +81,24 @@ module.exports = () => new Promise(async (res, rej) => {
|
||||
});
|
||||
//#endregion
|
||||
|
||||
const hots = (await Promise.all(hotsPromises))
|
||||
// タグを人気順に並べ替え
|
||||
let hots = (await Promise.all(hotsPromises))
|
||||
.filter(x => x != null)
|
||||
.sort((a, b) => b.count - a.count)
|
||||
.map(tag => tag.name)
|
||||
.slice(0, 5);
|
||||
.slice(0, max);
|
||||
|
||||
//#region 2で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
|
||||
//#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
|
||||
|
||||
//#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
|
||||
const countPromises: Array<Promise<any[]>> = [];
|
||||
|
||||
const range = 20;
|
||||
|
||||
Reference in New Issue
Block a user