Implement featured note injection
This commit is contained in:
		
							parent
							
								
									3d79e7a136
								
							
						
					
					
						commit
						d9986b7a2f
					
				
					 19 changed files with 117 additions and 16 deletions
				
			
		|  | @ -416,6 +416,7 @@ promotion: "プロモーション" | ||||||
| promote: "プロモート" | promote: "プロモート" | ||||||
| numberOfDays: "日数" | numberOfDays: "日数" | ||||||
| hideThisNote: "このノートを非表示" | hideThisNote: "このノートを非表示" | ||||||
|  | showFeaturedNotesInTimeline: "タイムラインにおすすめのノートを表示する" | ||||||
| 
 | 
 | ||||||
| _ago: | _ago: | ||||||
|   unknown: "謎" |   unknown: "謎" | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								migration/1582019042083-featured-injecttion.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1582019042083-featured-injecttion.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class featuredInjecttion1582019042083 implements MigrationInterface { | ||||||
|  |     name = 'featuredInjecttion1582019042083' | ||||||
|  | 
 | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" ADD "injectFeaturedNote" boolean NOT NULL DEFAULT true`, undefined); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<any> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "injectFeaturedNote"`, undefined); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| <sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed"> | <sequential-entrance class="sqadhkmv" ref="list" :direction="direction" :reversed="reversed"> | ||||||
| 	<template v-for="(item, i) in items"> | 	<template v-for="(item, i) in items"> | ||||||
| 		<slot :item="item" :i="i"></slot> | 		<slot :item="item" :i="i"></slot> | ||||||
| 		<div class="separator" :key="item.id + '_date'" v-if="i != items.length - 1 && new Date(item.createdAt).getDate() != new Date(items[i + 1].createdAt).getDate() && !item._prInjectionId_ && !items[i + 1]._prInjectionId_"> | 		<div class="separator" :key="item.id + '_date'" v-if="showDate(i, item)"> | ||||||
| 			<p class="date"> | 			<p class="date"> | ||||||
| 				<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span> | 				<span><fa class="icon" :icon="faAngleUp"/>{{ getDateText(item.createdAt) }}</span> | ||||||
| 				<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span> | 				<span>{{ getDateText(items[i + 1].createdAt) }}<fa class="icon" :icon="faAngleDown"/></span> | ||||||
|  | @ -52,6 +52,16 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  | 		showDate(i, item) { | ||||||
|  | 			return ( | ||||||
|  | 				i != this.items.length - 1 && | ||||||
|  | 				new Date(item.createdAt).getDate() != new Date(this.items[i + 1].createdAt).getDate() && | ||||||
|  | 				!item._prId_ && | ||||||
|  | 				!this.items[i + 1]._prId_ && | ||||||
|  | 				!item._featuredId_ && | ||||||
|  | 				!this.items[i + 1]._featuredId_); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
| 		focus() { | 		focus() { | ||||||
| 			this.$refs.list.focus(); | 			this.$refs.list.focus(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -10,7 +10,8 @@ | ||||||
| 	<x-sub v-for="note in conversation" :key="note.id" :note="note"/> | 	<x-sub v-for="note in conversation" :key="note.id" :note="note"/> | ||||||
| 	<x-sub :note="appearNote.reply" class="reply-to" v-if="appearNote.reply"/> | 	<x-sub :note="appearNote.reply" class="reply-to" v-if="appearNote.reply"/> | ||||||
| 	<div class="info" v-if="pinned"><fa :icon="faThumbtack"/> {{ $t('pinnedNote') }}</div> | 	<div class="info" v-if="pinned"><fa :icon="faThumbtack"/> {{ $t('pinnedNote') }}</div> | ||||||
| 	<div class="info" v-if="appearNote._prInjectionId_"><fa :icon="faBullhorn"/> {{ $t('promotion') }}<button class="_textButton hide" @click="readPromo()">{{ $t('hideThisNote') }}</button></div> | 	<div class="info" v-if="appearNote._prId_"><fa :icon="faBullhorn"/> {{ $t('promotion') }}<button class="_textButton hide" @click="readPromo()">{{ $t('hideThisNote') }} <fa :icon="faTimes"/></button></div> | ||||||
|  | 	<div class="info" v-if="appearNote._featuredId_"><fa :icon="faBolt"/> {{ $t('featured') }}</div> | ||||||
| 	<div class="renote" v-if="isRenote"> | 	<div class="renote" v-if="isRenote"> | ||||||
| 		<mk-avatar class="avatar" :user="note.user"/> | 		<mk-avatar class="avatar" :user="note.user"/> | ||||||
| 		<fa :icon="faRetweet"/> | 		<fa :icon="faRetweet"/> | ||||||
|  | @ -84,7 +85,7 @@ | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from 'vue'; | import Vue from 'vue'; | ||||||
| import { faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight } from '@fortawesome/free-solid-svg-icons'; | import { faBolt, faTimes, faBullhorn, faStar, faLink, faExternalLinkSquareAlt, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan, faQuoteRight } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | import { faCopy, faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import { parse } from '../../mfm/parse'; | import { parse } from '../../mfm/parse'; | ||||||
| import { sum, unique } from '../../prelude/array'; | import { sum, unique } from '../../prelude/array'; | ||||||
|  | @ -141,7 +142,7 @@ export default Vue.extend({ | ||||||
| 			replies: [], | 			replies: [], | ||||||
| 			showContent: false, | 			showContent: false, | ||||||
| 			hideThisNote: false, | 			hideThisNote: false, | ||||||
| 			faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan | 			faBolt, faTimes, faBullhorn, faPlus, faMinus, faRetweet, faReply, faReplyAll, faEllipsisH, faHome, faUnlock, faEnvelope, faThumbtack, faBan | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed"> | 	<x-list ref="notes" class="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed"> | ||||||
| 		<x-note :note="note" :detail="detail" :key="note._prInjectionId_ || note.id"/> | 		<x-note :note="note" :detail="detail" :key="note._featuredId_ || note._prId_ || note.id"/> | ||||||
| 	</x-list> | 	</x-list> | ||||||
| 
 | 
 | ||||||
| 	<div class="more" v-if="more && !reversed" style="margin-top: var(--margin);"> | 	<div class="more" v-if="more && !reversed" style="margin-top: var(--margin);"> | ||||||
|  |  | ||||||
|  | @ -13,6 +13,9 @@ | ||||||
| 			<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch"> | 			<mk-switch v-model="$store.state.i.autoWatch" @change="onChangeAutoWatch"> | ||||||
| 				{{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</template> | 				{{ $t('autoNoteWatch') }}<template #desc>{{ $t('autoNoteWatchDescription') }}</template> | ||||||
| 			</mk-switch> | 			</mk-switch> | ||||||
|  | 			<mk-switch v-model="$store.state.i.injectFeaturedNote" @change="onChangeInjectFeaturedNote"> | ||||||
|  | 				{{ $t('showFeaturedNotesInTimeline') }} | ||||||
|  | 			</mk-switch> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="_content"> | 		<div class="_content"> | ||||||
| 			<mk-button @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</mk-button> | 			<mk-button @click="readAllNotifications">{{ $t('markAsReadAllNotifications') }}</mk-button> | ||||||
|  | @ -84,6 +87,12 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  | 		onChangeInjectFeaturedNote(v) { | ||||||
|  | 			this.$root.api('i/update', { | ||||||
|  | 				injectFeaturedNote: v | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
| 		readAllUnreadNotes() { | 		readAllUnreadNotes() { | ||||||
| 			this.$root.api('i/read_all_unread_notes'); | 			this.$root.api('i/read_all_unread_notes'); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -125,6 +125,11 @@ export class UserProfile { | ||||||
| 	}) | 	}) | ||||||
| 	public carefulBot: boolean; | 	public carefulBot: boolean; | ||||||
| 
 | 
 | ||||||
|  | 	@Column('boolean', { | ||||||
|  | 		default: true, | ||||||
|  | 	}) | ||||||
|  | 	public injectFeaturedNote: boolean; | ||||||
|  | 
 | ||||||
| 	@Column({ | 	@Column({ | ||||||
| 		...id(), | 		...id(), | ||||||
| 		nullable: true | 		nullable: true | ||||||
|  |  | ||||||
|  | @ -196,7 +196,8 @@ export class NoteRepository extends Repository<Note> { | ||||||
| 			renoteId: note.renoteId, | 			renoteId: note.renoteId, | ||||||
| 			mentions: note.mentions.length > 0 ? note.mentions : undefined, | 			mentions: note.mentions.length > 0 ? note.mentions : undefined, | ||||||
| 			uri: note.uri || undefined, | 			uri: note.uri || undefined, | ||||||
| 			_prInjectionId_: (note as any)._prInjectionId_ || undefined, | 			_featuredId_: (note as any)._featuredId_ || undefined, | ||||||
|  | 			_prId_: (note as any)._prId_ || undefined, | ||||||
| 
 | 
 | ||||||
| 			...(opts.detail ? { | 			...(opts.detail ? { | ||||||
| 				reply: note.replyId ? this.pack(note.replyId, meId, { | 				reply: note.replyId ? this.pack(note.replyId, meId, { | ||||||
|  |  | ||||||
|  | @ -227,6 +227,7 @@ export class UserRepository extends Repository<User> { | ||||||
| 				avatarId: user.avatarId, | 				avatarId: user.avatarId, | ||||||
| 				bannerId: user.bannerId, | 				bannerId: user.bannerId, | ||||||
| 				autoWatch: profile!.autoWatch, | 				autoWatch: profile!.autoWatch, | ||||||
|  | 				injectFeaturedNote: profile!.injectFeaturedNote, | ||||||
| 				alwaysMarkNsfw: profile!.alwaysMarkNsfw, | 				alwaysMarkNsfw: profile!.alwaysMarkNsfw, | ||||||
| 				carefulBot: profile!.carefulBot, | 				carefulBot: profile!.carefulBot, | ||||||
| 				autoAcceptFollowed: profile!.autoAcceptFollowed, | 				autoAcceptFollowed: profile!.autoAcceptFollowed, | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								src/server/api/common/inject-featured.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/server/api/common/inject-featured.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | import rndstr from 'rndstr'; | ||||||
|  | import { Note } from '../../../models/entities/note'; | ||||||
|  | import { User } from '../../../models/entities/user'; | ||||||
|  | import { Notes, UserProfiles } from '../../../models'; | ||||||
|  | import { generateMuteQuery } from './generate-mute-query'; | ||||||
|  | import { ensure } from '../../../prelude/ensure'; | ||||||
|  | 
 | ||||||
|  | // TODO: リアクション、Renote、返信などをしたノートは除外する
 | ||||||
|  | 
 | ||||||
|  | export async function injectFeatured(timeline: Note[], user?: User | null) { | ||||||
|  | 	if (timeline.length < 5) return; | ||||||
|  | 
 | ||||||
|  | 	if (user) { | ||||||
|  | 		const profile = await UserProfiles.findOne(user.id).then(ensure); | ||||||
|  | 		if (!profile.injectFeaturedNote) return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const max = 30; | ||||||
|  | 	const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
 | ||||||
|  | 
 | ||||||
|  | 	const query = Notes.createQueryBuilder('note') | ||||||
|  | 		.addSelect('note.score') | ||||||
|  | 		.where('note.userHost IS NULL') | ||||||
|  | 		.andWhere(`note.score > 0`) | ||||||
|  | 		.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) | ||||||
|  | 		.andWhere(`note.visibility = 'public'`) | ||||||
|  | 		.leftJoinAndSelect('note.user', 'user'); | ||||||
|  | 
 | ||||||
|  | 	if (user) generateMuteQuery(query, user); | ||||||
|  | 
 | ||||||
|  | 	const notes = await query | ||||||
|  | 		.orderBy('note.score', 'DESC') | ||||||
|  | 		.take(max) | ||||||
|  | 		.getMany(); | ||||||
|  | 
 | ||||||
|  | 	if (notes.length === 0) return; | ||||||
|  | 
 | ||||||
|  | 	// Pick random one
 | ||||||
|  | 	const featured = notes[Math.floor(Math.random() * notes.length)]; | ||||||
|  | 
 | ||||||
|  | 	(featured as any)._featuredId_ = rndstr('a-z0-9', 8); | ||||||
|  | 
 | ||||||
|  | 	// Inject featured
 | ||||||
|  | 	timeline.splice(3, 0, featured); | ||||||
|  | } | ||||||
|  | @ -4,14 +4,14 @@ import { User } from '../../../models/entities/user'; | ||||||
| import { PromoReads, PromoNotes, Notes, Users } from '../../../models'; | import { PromoReads, PromoNotes, Notes, Users } from '../../../models'; | ||||||
| import { ensure } from '../../../prelude/ensure'; | import { ensure } from '../../../prelude/ensure'; | ||||||
| 
 | 
 | ||||||
| export async function injectPromo(user: User, timeline: Note[]) { | export async function injectPromo(timeline: Note[], user?: User | null) { | ||||||
| 	if (timeline.length < 5) return; | 	if (timeline.length < 5) return; | ||||||
| 
 | 
 | ||||||
| 	// TODO: readやexpireフィルタはクエリ側でやる
 | 	// TODO: readやexpireフィルタはクエリ側でやる
 | ||||||
| 
 | 
 | ||||||
| 	const reads = await PromoReads.find({ | 	const reads = user ? await PromoReads.find({ | ||||||
| 		userId: user.id | 		userId: user.id | ||||||
| 	}); | 	}) : []; | ||||||
| 
 | 
 | ||||||
| 	let promos = await PromoNotes.find(); | 	let promos = await PromoNotes.find(); | ||||||
| 
 | 
 | ||||||
|  | @ -20,15 +20,15 @@ export async function injectPromo(user: User, timeline: Note[]) { | ||||||
| 
 | 
 | ||||||
| 	if (promos.length === 0) return; | 	if (promos.length === 0) return; | ||||||
| 
 | 
 | ||||||
|  | 	// Pick random promo
 | ||||||
| 	const promo = promos[Math.floor(Math.random() * promos.length)]; | 	const promo = promos[Math.floor(Math.random() * promos.length)]; | ||||||
| 
 | 
 | ||||||
| 	// Pick random promo
 |  | ||||||
| 	const note = await Notes.findOne(promo.noteId).then(ensure); | 	const note = await Notes.findOne(promo.noteId).then(ensure); | ||||||
| 
 | 
 | ||||||
| 	// Join
 | 	// Join
 | ||||||
| 	note.user = await Users.findOne(note.userId).then(ensure); | 	note.user = await Users.findOne(note.userId).then(ensure); | ||||||
| 
 | 
 | ||||||
| 	(note as any)._prInjectionId_ = rndstr('a-z0-9', 8); | 	(note as any)._prId_ = rndstr('a-z0-9', 8); | ||||||
| 
 | 
 | ||||||
| 	// Inject promo
 | 	// Inject promo
 | ||||||
| 	timeline.splice(3, 0, note); | 	timeline.splice(3, 0, note); | ||||||
|  |  | ||||||
|  | @ -88,7 +88,6 @@ export async function signup(username: User['username'], password: UserProfile[' | ||||||
| 		await transactionalEntityManager.save(new UserProfile({ | 		await transactionalEntityManager.save(new UserProfile({ | ||||||
| 			userId: account.id, | 			userId: account.id, | ||||||
| 			autoAcceptFollowed: true, | 			autoAcceptFollowed: true, | ||||||
| 			autoWatch: false, |  | ||||||
| 			password: hash, | 			password: hash, | ||||||
| 		})); | 		})); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { ApiError } from './error'; | ||||||
| import { App } from '../../models/entities/app'; | import { App } from '../../models/entities/app'; | ||||||
| import { SchemaType } from '../../misc/schema'; | import { SchemaType } from '../../misc/schema'; | ||||||
| 
 | 
 | ||||||
|  | // TODO: defaultが設定されている場合はその型も考慮する
 | ||||||
| type Params<T extends IEndpointMeta> = { | type Params<T extends IEndpointMeta> = { | ||||||
| 	[P in keyof T['params']]: NonNullable<T['params']>[P]['transform'] extends Function | 	[P in keyof T['params']]: NonNullable<T['params']>[P]['transform'] extends Function | ||||||
| 		? ReturnType<NonNullable<T['params']>[P]['transform']> | 		? ReturnType<NonNullable<T['params']>[P]['transform']> | ||||||
|  |  | ||||||
|  | @ -126,6 +126,10 @@ export const meta = { | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  | 		injectFeaturedNote: { | ||||||
|  | 			validator: $.optional.bool, | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
| 		alwaysMarkNsfw: { | 		alwaysMarkNsfw: { | ||||||
| 			validator: $.optional.bool, | 			validator: $.optional.bool, | ||||||
| 			desc: { | 			desc: { | ||||||
|  | @ -195,6 +199,7 @@ export default define(meta, async (ps, user, app) => { | ||||||
| 	if (typeof ps.autoAcceptFollowed == 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; | 	if (typeof ps.autoAcceptFollowed == 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; | ||||||
| 	if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; | 	if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; | ||||||
| 	if (typeof ps.autoWatch == 'boolean') profileUpdates.autoWatch = ps.autoWatch; | 	if (typeof ps.autoWatch == 'boolean') profileUpdates.autoWatch = ps.autoWatch; | ||||||
|  | 	if (typeof ps.injectFeaturedNote == 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; | ||||||
| 	if (typeof ps.alwaysMarkNsfw == 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; | 	if (typeof ps.alwaysMarkNsfw == 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; | ||||||
| 
 | 
 | ||||||
| 	if (ps.avatarId) { | 	if (ps.avatarId) { | ||||||
|  |  | ||||||
|  | @ -46,6 +46,7 @@ export default define(meta, async (ps, user) => { | ||||||
| 	const query = Notes.createQueryBuilder('note') | 	const query = Notes.createQueryBuilder('note') | ||||||
| 		.addSelect('note.score') | 		.addSelect('note.score') | ||||||
| 		.where('note.userHost IS NULL') | 		.where('note.userHost IS NULL') | ||||||
|  | 		.andWhere(`note.score > 0`) | ||||||
| 		.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) | 		.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) | ||||||
| 		.andWhere(`note.visibility = 'public'`) | 		.andWhere(`note.visibility = 'public'`) | ||||||
| 		.leftJoinAndSelect('note.user', 'user'); | 		.leftJoinAndSelect('note.user', 'user'); | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { generateMuteQuery } from '../../common/generate-mute-query'; | ||||||
| import { activeUsersChart } from '../../../../services/chart'; | import { activeUsersChart } from '../../../../services/chart'; | ||||||
| import { generateRepliesQuery } from '../../common/generate-replies-query'; | import { generateRepliesQuery } from '../../common/generate-replies-query'; | ||||||
| import { injectPromo } from '../../common/inject-promo'; | import { injectPromo } from '../../common/inject-promo'; | ||||||
|  | import { injectFeatured } from '../../common/inject-featured'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -90,7 +91,8 @@ export default define(meta, async (ps, user) => { | ||||||
| 
 | 
 | ||||||
| 	const timeline = await query.take(ps.limit!).getMany(); | 	const timeline = await query.take(ps.limit!).getMany(); | ||||||
| 
 | 
 | ||||||
| 	await injectPromo(user, timeline); | 	await injectPromo(timeline, user); | ||||||
|  | 	await injectFeatured(timeline, user); | ||||||
| 
 | 
 | ||||||
| 	process.nextTick(() => { | 	process.nextTick(() => { | ||||||
| 		if (user) { | 		if (user) { | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import { generateMuteQuery } from '../../common/generate-mute-query'; | ||||||
| import { activeUsersChart } from '../../../../services/chart'; | import { activeUsersChart } from '../../../../services/chart'; | ||||||
| import { generateRepliesQuery } from '../../common/generate-replies-query'; | import { generateRepliesQuery } from '../../common/generate-replies-query'; | ||||||
| import { injectPromo } from '../../common/inject-promo'; | import { injectPromo } from '../../common/inject-promo'; | ||||||
|  | import { injectFeatured } from '../../common/inject-featured'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -170,7 +171,8 @@ export default define(meta, async (ps, user) => { | ||||||
| 
 | 
 | ||||||
| 	const timeline = await query.take(ps.limit!).getMany(); | 	const timeline = await query.take(ps.limit!).getMany(); | ||||||
| 
 | 
 | ||||||
| 	await injectPromo(user, timeline); | 	await injectPromo(timeline, user); | ||||||
|  | 	await injectFeatured(timeline, user); | ||||||
| 
 | 
 | ||||||
| 	process.nextTick(() => { | 	process.nextTick(() => { | ||||||
| 		if (user) { | 		if (user) { | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import { activeUsersChart } from '../../../../services/chart'; | ||||||
| import { Brackets } from 'typeorm'; | import { Brackets } from 'typeorm'; | ||||||
| import { generateRepliesQuery } from '../../common/generate-replies-query'; | import { generateRepliesQuery } from '../../common/generate-replies-query'; | ||||||
| import { injectPromo } from '../../common/inject-promo'; | import { injectPromo } from '../../common/inject-promo'; | ||||||
|  | import { injectFeatured } from '../../common/inject-featured'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -123,7 +124,8 @@ export default define(meta, async (ps, user) => { | ||||||
| 
 | 
 | ||||||
| 	const timeline = await query.take(ps.limit!).getMany(); | 	const timeline = await query.take(ps.limit!).getMany(); | ||||||
| 
 | 
 | ||||||
| 	await injectPromo(user, timeline); | 	await injectPromo(timeline, user); | ||||||
|  | 	await injectFeatured(timeline, user); | ||||||
| 
 | 
 | ||||||
| 	process.nextTick(() => { | 	process.nextTick(() => { | ||||||
| 		if (user) { | 		if (user) { | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { activeUsersChart } from '../../../../services/chart'; | ||||||
| import { Brackets } from 'typeorm'; | import { Brackets } from 'typeorm'; | ||||||
| import { generateRepliesQuery } from '../../common/generate-replies-query'; | import { generateRepliesQuery } from '../../common/generate-replies-query'; | ||||||
| import { injectPromo } from '../../common/inject-promo'; | import { injectPromo } from '../../common/inject-promo'; | ||||||
|  | import { injectFeatured } from '../../common/inject-featured'; | ||||||
| 
 | 
 | ||||||
| export const meta = { | export const meta = { | ||||||
| 	desc: { | 	desc: { | ||||||
|  | @ -156,7 +157,8 @@ export default define(meta, async (ps, user) => { | ||||||
| 
 | 
 | ||||||
| 	const timeline = await query.take(ps.limit!).getMany(); | 	const timeline = await query.take(ps.limit!).getMany(); | ||||||
| 
 | 
 | ||||||
| 	await injectPromo(user, timeline); | 	await injectPromo(timeline, user); | ||||||
|  | 	await injectFeatured(timeline, user); | ||||||
| 
 | 
 | ||||||
| 	process.nextTick(() => { | 	process.nextTick(() => { | ||||||
| 		if (user) { | 		if (user) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue