wip
This commit is contained in:
		
							parent
							
								
									3d9ac6387e
								
							
						
					
					
						commit
						c2e053a208
					
				|  | @ -28,6 +28,7 @@ import MkUser from './views/pages/user/user.vue'; | |||
| import MkFavorites from './views/pages/favorites.vue'; | ||||
| import MkSelectDrive from './views/pages/selectdrive.vue'; | ||||
| import MkDrive from './views/pages/drive.vue'; | ||||
| import MkUserList from './views/pages/user-list.vue'; | ||||
| import MkHomeCustomize from './views/pages/home-customize.vue'; | ||||
| import MkMessagingRoom from './views/pages/messaging-room.vue'; | ||||
| import MkNote from './views/pages/note.vue'; | ||||
|  | @ -55,6 +56,7 @@ init(async (launch) => { | |||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||
| 			{ path: '/i/drive', component: MkDrive }, | ||||
| 			{ path: '/i/drive/folder/:folder', component: MkDrive }, | ||||
| 			{ path: '/i/lists/:list', component: MkUserList }, | ||||
| 			{ path: '/selectdrive', component: MkSelectDrive }, | ||||
| 			{ path: '/search', component: MkSearch }, | ||||
| 			{ path: '/othello', component: MkOthello }, | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import friendsMaker from './friends-maker.vue'; | |||
| import followers from './followers.vue'; | ||||
| import following from './following.vue'; | ||||
| import usersList from './users-list.vue'; | ||||
| import userListTimeline from './user-list-timeline.vue'; | ||||
| import widgetContainer from './widget-container.vue'; | ||||
| 
 | ||||
| Vue.component('mk-ui', ui); | ||||
|  | @ -58,4 +59,5 @@ Vue.component('mk-friends-maker', friendsMaker); | |||
| Vue.component('mk-followers', followers); | ||||
| Vue.component('mk-following', following); | ||||
| Vue.component('mk-users-list', usersList); | ||||
| Vue.component('mk-user-list-timeline', userListTimeline); | ||||
| Vue.component('mk-widget-container', widgetContainer); | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <mk-window ref="window" is-modal width="500px" height="550px" @closed="$destroy"> | ||||
| 	<span slot="header" :class="$style.header">%fa:list% リスト</span> | ||||
| 	<span slot="header">%fa:list% リスト</span> | ||||
| 
 | ||||
| 	<button class="ui" @click="add">リストを作成</button> | ||||
| 	<router-link v-for="list in lists" :key="list.id" :to="`/i/lists/${list.id}`">{{ list.title }}</router-link> | ||||
|  |  | |||
|  | @ -1,5 +1,14 @@ | |||
| <template> | ||||
| <div class="mk-notes"> | ||||
| 	<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div> | ||||
| 
 | ||||
| 	<slot name="empty" v-if="notes.length == 0 && !fetching && requestInitPromise == null"></slot> | ||||
| 
 | ||||
| 	<div v-if="!fetching && requestInitPromise != null"> | ||||
| 		<p>読み込みに失敗しました。</p> | ||||
| 		<button @click="resolveInitPromise">リトライ</button> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<transition-group name="mk-notes" class="transition"> | ||||
| 		<template v-for="(note, i) in _notes"> | ||||
| 			<x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> | ||||
|  | @ -9,7 +18,8 @@ | |||
| 			</p> | ||||
| 		</template> | ||||
| 	</transition-group> | ||||
| 	<footer v-if="loadMore"> | ||||
| 
 | ||||
| 	<footer v-if="more"> | ||||
| 		<button @click="loadMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> | ||||
| 			<template v-if="!moreFetching">%i18n:@load-more%</template> | ||||
| 			<template v-if="moreFetching">%fa:spinner .pulse .fw%</template> | ||||
|  | @ -40,9 +50,10 @@ export default Vue.extend({ | |||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			requestInitPromise: null as () => Promise<any[]>, | ||||
| 			notes: [], | ||||
| 			queue: [], | ||||
| 			fetching: false, | ||||
| 			fetching: true, | ||||
| 			moreFetching: false | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -80,9 +91,25 @@ export default Vue.extend({ | |||
| 			Vue.set((this as any).notes, i, note); | ||||
| 		}, | ||||
| 
 | ||||
| 		init(notes) { | ||||
| 		init(promiseGenerator: () => Promise<any[]>) { | ||||
| 			this.requestInitPromise = promiseGenerator; | ||||
| 			this.resolveInitPromise(); | ||||
| 		}, | ||||
| 
 | ||||
| 		resolveInitPromise() { | ||||
| 			this.queue = []; | ||||
| 			this.notes = notes; | ||||
| 			this.notes = []; | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			const promise = this.requestInitPromise(); | ||||
| 
 | ||||
| 			promise.then(notes => { | ||||
| 				this.notes = notes; | ||||
| 				this.requestInitPromise = null; | ||||
| 				this.fetching = false; | ||||
| 			}, e => { | ||||
| 				this.fetching = false; | ||||
| 			}); | ||||
| 		}, | ||||
| 
 | ||||
| 		prepend(note, silent = false) { | ||||
|  | @ -137,6 +164,9 @@ export default Vue.extend({ | |||
| 		}, | ||||
| 
 | ||||
| 		async loadMore() { | ||||
| 			if (this.more == null) return; | ||||
| 			if (this.moreFetching) return; | ||||
| 
 | ||||
| 			this.moreFetching = true; | ||||
| 			await this.more(); | ||||
| 			this.moreFetching = false; | ||||
|  | @ -157,6 +187,8 @@ export default Vue.extend({ | |||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| @import '~const.styl' | ||||
| 
 | ||||
| root(isDark) | ||||
| 	.transition | ||||
| 		.mk-notes-enter | ||||
|  | @ -183,6 +215,13 @@ root(isDark) | |||
| 			[data-fa] | ||||
| 				margin-right 8px | ||||
| 
 | ||||
| 	> .newer-indicator | ||||
| 		position -webkit-sticky | ||||
| 		position sticky | ||||
| 		z-index 100 | ||||
| 		height 3px | ||||
| 		background $theme-color | ||||
| 
 | ||||
| 	> footer | ||||
| 		> * | ||||
| 			display block | ||||
|  | @ -191,16 +230,16 @@ root(isDark) | |||
| 			width 100% | ||||
| 			text-align center | ||||
| 			color #ccc | ||||
| 			border-top solid 1px #eaeaea | ||||
| 			border-top solid 1px isDark ? #1c2023 : #eaeaea | ||||
| 			border-bottom-left-radius 4px | ||||
| 			border-bottom-right-radius 4px | ||||
| 
 | ||||
| 		> button | ||||
| 			&:hover | ||||
| 				background #f5f5f5 | ||||
| 				background isDark ? #2e3440 : #f5f5f5 | ||||
| 
 | ||||
| 			&:active | ||||
| 				background #eee | ||||
| 				background isDark ? #21242b : #eee | ||||
| 
 | ||||
| .mk-notes[data-darkmode] | ||||
| 	root(true) | ||||
|  |  | |||
|  | @ -1,14 +1,15 @@ | |||
| <template> | ||||
| <div class="mk-timeline-core"> | ||||
| 	<div class="newer-indicator" :style="{ top: $store.state.uiHeaderHeight + 'px' }" v-show="queue.length > 0"></div> | ||||
| 	<mk-friends-maker v-if="src == 'home' && alone"/> | ||||
| 	<div class="fetching" v-if="fetching"> | ||||
| 		<mk-ellipsis-icon/> | ||||
| 	</div> | ||||
| 	<p class="empty" v-if="notes.length == 0 && !fetching"> | ||||
| 		%fa:R comments%%i18n:@empty% | ||||
| 	</p> | ||||
| 	<mk-notes :notes="notes" ref="timeline" :more="canFetchMore ? more : null"/> | ||||
| 
 | ||||
| 	<mk-notes ref="timeline" :more="canFetchMore ? more : null"> | ||||
| 		<p :class="$style.empty" slot="empty"> | ||||
| 			%fa:R comments%%i18n:@empty% | ||||
| 		</p> | ||||
| 	</mk-notes> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -89,28 +90,26 @@ export default Vue.extend({ | |||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		isScrollTop() { | ||||
| 			return window.scrollY <= 8; | ||||
| 		}, | ||||
| 
 | ||||
| 		fetch(cb?) { | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api(this.endpoint, { | ||||
| 				limit: fetchLimit + 1, | ||||
| 				untilDate: this.date ? this.date.getTime() : undefined, | ||||
| 				includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, | ||||
| 				includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == fetchLimit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} | ||||
| 				(this.$refs.timeline as any).init(notes); | ||||
| 				this.fetching = false; | ||||
| 				this.$emit('loaded'); | ||||
| 				if (cb) cb(); | ||||
| 			}); | ||||
| 			(this.$refs.timeline as any).init(() => new Promise((res, rej) => { | ||||
| 				(this as any).api(this.endpoint, { | ||||
| 					limit: fetchLimit + 1, | ||||
| 					untilDate: this.date ? this.date.getTime() : undefined, | ||||
| 					includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, | ||||
| 					includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes | ||||
| 				}).then(notes => { | ||||
| 					if (notes.length == fetchLimit + 1) { | ||||
| 						notes.pop(); | ||||
| 						this.existMore = true; | ||||
| 					} | ||||
| 					res(notes); | ||||
| 					this.fetching = false; | ||||
| 					this.$emit('loaded'); | ||||
| 					if (cb) cb(); | ||||
| 				}, rej); | ||||
| 			})); | ||||
| 		}, | ||||
| 
 | ||||
| 		more() { | ||||
|  | @ -167,31 +166,27 @@ export default Vue.extend({ | |||
| @import '~const.styl' | ||||
| 
 | ||||
| .mk-timeline-core | ||||
| 	> .newer-indicator | ||||
| 		position -webkit-sticky | ||||
| 		position sticky | ||||
| 		z-index 100 | ||||
| 		height 3px | ||||
| 		background $theme-color | ||||
| 
 | ||||
| 	> .mk-friends-maker | ||||
| 		border-bottom solid 1px #eee | ||||
| 
 | ||||
| 	> .fetching | ||||
| 		padding 64px 0 | ||||
| 
 | ||||
| 	> .empty | ||||
| 		display block | ||||
| 		margin 0 auto | ||||
| 		padding 32px | ||||
| 		max-width 400px | ||||
| 		text-align center | ||||
| 		color #999 | ||||
| </style> | ||||
| 
 | ||||
| 		> [data-fa] | ||||
| 			display block | ||||
| 			margin-bottom 16px | ||||
| 			font-size 3em | ||||
| 			color #ccc | ||||
| <style lang="stylus" module> | ||||
| .empty | ||||
| 	display block | ||||
| 	margin 0 auto | ||||
| 	padding 32px | ||||
| 	max-width 400px | ||||
| 	text-align center | ||||
| 	color #999 | ||||
| 
 | ||||
| 	> [data-fa] | ||||
| 		display block | ||||
| 		margin-bottom 16px | ||||
| 		font-size 3em | ||||
| 		color #ccc | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
| 	<mk-notes ref="timeline" :more="more"/> | ||||
| 	<mk-notes ref="timeline" :more="existMore ? more : null"/> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|  | @ -19,42 +19,49 @@ export default Vue.extend({ | |||
| 		}; | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		$route: 'fetch' | ||||
| 		$route: 'init' | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 		this.init(); | ||||
| 	}, | ||||
| 	beforeDestroy() { | ||||
| 		this.connection.close(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		fetch() { | ||||
| 		init() { | ||||
| 			if (this.connection) this.connection.close(); | ||||
| 			this.connection = new UserListStream((this as any).os, (this as any).os.i, this.list.id); | ||||
| 			this.connection.on('note', this.onNote); | ||||
| 			this.connection.on('userAdded', this.onUserAdded); | ||||
| 			this.connection.on('userRemoved', this.onUserRemoved); | ||||
| 
 | ||||
| 			this.fetch(); | ||||
| 		}, | ||||
| 		fetch() { | ||||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api('notes/list-timeline', { | ||||
| 				limit: fetchLimit + 1, | ||||
| 				includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, | ||||
| 				includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes | ||||
| 			}).then(notes => { | ||||
| 				if (notes.length == fetchLimit + 1) { | ||||
| 					notes.pop(); | ||||
| 					this.existMore = true; | ||||
| 				} | ||||
| 				(this.$refs.timeline as any).init(notes); | ||||
| 				this.fetching = false; | ||||
| 				this.$emit('loaded'); | ||||
| 			}); | ||||
| 			(this.$refs.timeline as any).init(() => new Promise((res, rej) => { | ||||
| 				(this as any).api('notes/user-list-timeline', { | ||||
| 					listId: this.list.id, | ||||
| 					limit: fetchLimit + 1, | ||||
| 					includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, | ||||
| 					includeRenotedMyNotes: (this as any).os.i.clientSettings.showRenotedMyNotes | ||||
| 				}).then(notes => { | ||||
| 					if (notes.length == fetchLimit + 1) { | ||||
| 						notes.pop(); | ||||
| 						this.existMore = true; | ||||
| 					} | ||||
| 					res(notes); | ||||
| 					this.fetching = false; | ||||
| 					this.$emit('loaded'); | ||||
| 				}, rej); | ||||
| 			})); | ||||
| 		}, | ||||
| 		more() { | ||||
| 			this.moreFetching = true; | ||||
| 
 | ||||
| 			(this as any).api('notes/list-timeline', { | ||||
| 				listId: this.list.id, | ||||
| 				limit: fetchLimit + 1, | ||||
| 				untilId: (this.$refs.timeline as any).tail().id, | ||||
| 				includeMyRenotes: (this as any).os.i.clientSettings.showMyRenotes, | ||||
|  | @ -68,7 +75,17 @@ export default Vue.extend({ | |||
| 				notes.forEach(n => (this.$refs.timeline as any).append(n)); | ||||
| 				this.moreFetching = false; | ||||
| 			}); | ||||
| 		} | ||||
| 		}, | ||||
| 		onNote(note) { | ||||
| 			// Prepend a note | ||||
| 			(this.$refs.timeline as any).prepend(note); | ||||
| 		}, | ||||
| 		onUserAdded() { | ||||
| 			this.fetch(); | ||||
| 		}, | ||||
| 		onUserRemoved() { | ||||
| 			this.fetch(); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | @ -1,9 +1,11 @@ | |||
| <template> | ||||
| <mk-ui> | ||||
| 	<header :class="$style.header"> | ||||
| 		<h1>{{ list.title }}</h1> | ||||
| 	</header> | ||||
| 	<mk-list-timeline :list="list"/> | ||||
| 	<template v-if="!fetching"> | ||||
| 		<header :class="$style.header"> | ||||
| 			<h1>{{ list.title }}</h1> | ||||
| 		</header> | ||||
| 		<mk-user-list-timeline :list="list"/> | ||||
| 	</template> | ||||
| </mk-ui> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -28,7 +30,7 @@ export default Vue.extend({ | |||
| 			this.fetching = true; | ||||
| 
 | ||||
| 			(this as any).api('users/lists/show', { | ||||
| 				id: this.$route.params.list | ||||
| 				listId: this.$route.params.list | ||||
| 			}).then(list => { | ||||
| 				this.list = list; | ||||
| 				this.fetching = false; | ||||
|  | @ -414,6 +414,27 @@ const endpoints: Endpoint[] = [ | |||
| 		name: 'users/get_frequently_replied_users' | ||||
| 	}, | ||||
| 
 | ||||
| 	{ | ||||
| 		name: 'users/lists/show', | ||||
| 		withCredential: true, | ||||
| 		kind: 'account-read' | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'users/lists/create', | ||||
| 		withCredential: true, | ||||
| 		kind: 'account-write' | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'users/lists/push', | ||||
| 		withCredential: true, | ||||
| 		kind: 'account-write' | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'users/lists/list', | ||||
| 		withCredential: true, | ||||
| 		kind: 'account-read' | ||||
| 	}, | ||||
| 
 | ||||
| 	{ | ||||
| 		name: 'following/create', | ||||
| 		withCredential: true, | ||||
|  | @ -503,6 +524,14 @@ const endpoints: Endpoint[] = [ | |||
| 			max: 100 | ||||
| 		} | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'notes/user-list-timeline', | ||||
| 		withCredential: true, | ||||
| 		limit: { | ||||
| 			duration: ms('10minutes'), | ||||
| 			max: 100 | ||||
| 		} | ||||
| 	}, | ||||
| 	{ | ||||
| 		name: 'notes/mentions', | ||||
| 		withCredential: true, | ||||
|  |  | |||
|  | @ -0,0 +1,179 @@ | |||
| /** | ||||
|  * Module dependencies | ||||
|  */ | ||||
| import $ from 'cafy'; import ID from '../../../../cafy-id'; | ||||
| import Note from '../../../../models/note'; | ||||
| import Mute from '../../../../models/mute'; | ||||
| import { pack } from '../../../../models/note'; | ||||
| import UserList from '../../../../models/user-list'; | ||||
| 
 | ||||
| /** | ||||
|  * Get timeline of a user list | ||||
|  */ | ||||
| module.exports = async (params, user, app) => { | ||||
| 	// Get 'limit' parameter
 | ||||
| 	const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; | ||||
| 	if (limitErr) throw 'invalid limit param'; | ||||
| 
 | ||||
| 	// Get 'sinceId' parameter
 | ||||
| 	const [sinceId, sinceIdErr] = $(params.sinceId).optional.type(ID).$; | ||||
| 	if (sinceIdErr) throw 'invalid sinceId param'; | ||||
| 
 | ||||
| 	// Get 'untilId' parameter
 | ||||
| 	const [untilId, untilIdErr] = $(params.untilId).optional.type(ID).$; | ||||
| 	if (untilIdErr) throw 'invalid untilId param'; | ||||
| 
 | ||||
| 	// Get 'sinceDate' parameter
 | ||||
| 	const [sinceDate, sinceDateErr] = $(params.sinceDate).optional.number().$; | ||||
| 	if (sinceDateErr) throw 'invalid sinceDate param'; | ||||
| 
 | ||||
| 	// Get 'untilDate' parameter
 | ||||
| 	const [untilDate, untilDateErr] = $(params.untilDate).optional.number().$; | ||||
| 	if (untilDateErr) throw 'invalid untilDate param'; | ||||
| 
 | ||||
| 	// Check if only one of sinceId, untilId, sinceDate, untilDate specified
 | ||||
| 	if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) { | ||||
| 		throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified'; | ||||
| 	} | ||||
| 
 | ||||
| 	// Get 'includeMyRenotes' parameter
 | ||||
| 	const [includeMyRenotes = true, includeMyRenotesErr] = $(params.includeMyRenotes).optional.boolean().$; | ||||
| 	if (includeMyRenotesErr) throw 'invalid includeMyRenotes param'; | ||||
| 
 | ||||
| 	// Get 'includeRenotedMyNotes' parameter
 | ||||
| 	const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $(params.includeRenotedMyNotes).optional.boolean().$; | ||||
| 	if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param'; | ||||
| 
 | ||||
| 	// Get 'listId' parameter
 | ||||
| 	const [listId, listIdErr] = $(params.listId).type(ID).$; | ||||
| 	if (listIdErr) throw 'invalid listId param'; | ||||
| 
 | ||||
| 	const [list, mutedUserIds] = await Promise.all([ | ||||
| 		// リストを取得
 | ||||
| 		// Fetch the list
 | ||||
| 		UserList.findOne({ | ||||
| 			_id: listId, | ||||
| 			userId: user._id | ||||
| 		}), | ||||
| 
 | ||||
| 		// ミュートしているユーザーを取得
 | ||||
| 		Mute.find({ | ||||
| 			muterId: user._id | ||||
| 		}).then(ms => ms.map(m => m.muteeId)) | ||||
| 	]); | ||||
| 
 | ||||
| 	if (list.userIds.length == 0) { | ||||
| 		return []; | ||||
| 	} | ||||
| 
 | ||||
| 	//#region Construct query
 | ||||
| 	const sort = { | ||||
| 		_id: -1 | ||||
| 	}; | ||||
| 
 | ||||
| 	const listQuery = list.userIds.map(u => ({ | ||||
| 		userId: u, | ||||
| 
 | ||||
| 		// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める)
 | ||||
| 		$or: [{ | ||||
| 			// リプライでない
 | ||||
| 			replyId: null | ||||
| 		}, { // または
 | ||||
| 			// リプライだが返信先が投稿者自身の投稿
 | ||||
| 			$expr: { | ||||
| 				$eq: ['$_reply.userId', '$userId'] | ||||
| 			} | ||||
| 		}, { // または
 | ||||
| 			// リプライだが返信先が自分(フォロワー)の投稿
 | ||||
| 			'_reply.userId': user._id | ||||
| 		}, { // または
 | ||||
| 			// 自分(フォロワー)が送信したリプライ
 | ||||
| 			userId: user._id | ||||
| 		}] | ||||
| 	})); | ||||
| 
 | ||||
| 	const query = { | ||||
| 		$and: [{ | ||||
| 			// リストに入っている人のタイムラインへの投稿
 | ||||
| 			$or: listQuery, | ||||
| 
 | ||||
| 			// mute
 | ||||
| 			userId: { | ||||
| 				$nin: mutedUserIds | ||||
| 			}, | ||||
| 			'_reply.userId': { | ||||
| 				$nin: mutedUserIds | ||||
| 			}, | ||||
| 			'_renote.userId': { | ||||
| 				$nin: mutedUserIds | ||||
| 			}, | ||||
| 		}] | ||||
| 	} as any; | ||||
| 
 | ||||
| 	// MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
 | ||||
| 	// つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
 | ||||
| 	// for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
 | ||||
| 
 | ||||
| 	if (includeMyRenotes === false) { | ||||
| 		query.$and.push({ | ||||
| 			$or: [{ | ||||
| 				userId: { $ne: user._id } | ||||
| 			}, { | ||||
| 				renoteId: null | ||||
| 			}, { | ||||
| 				text: { $ne: null } | ||||
| 			}, { | ||||
| 				mediaIds: { $ne: [] } | ||||
| 			}, { | ||||
| 				poll: { $ne: null } | ||||
| 			}] | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	if (includeRenotedMyNotes === false) { | ||||
| 		query.$and.push({ | ||||
| 			$or: [{ | ||||
| 				'_renote.userId': { $ne: user._id } | ||||
| 			}, { | ||||
| 				renoteId: null | ||||
| 			}, { | ||||
| 				text: { $ne: null } | ||||
| 			}, { | ||||
| 				mediaIds: { $ne: [] } | ||||
| 			}, { | ||||
| 				poll: { $ne: null } | ||||
| 			}] | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	if (sinceId) { | ||||
| 		sort._id = 1; | ||||
| 		query._id = { | ||||
| 			$gt: sinceId | ||||
| 		}; | ||||
| 	} else if (untilId) { | ||||
| 		query._id = { | ||||
| 			$lt: untilId | ||||
| 		}; | ||||
| 	} else if (sinceDate) { | ||||
| 		sort._id = 1; | ||||
| 		query.createdAt = { | ||||
| 			$gt: new Date(sinceDate) | ||||
| 		}; | ||||
| 	} else if (untilDate) { | ||||
| 		query.createdAt = { | ||||
| 			$lt: new Date(untilDate) | ||||
| 		}; | ||||
| 	} | ||||
| 	//#endregion
 | ||||
| 
 | ||||
| 	// Issue query
 | ||||
| 	const timeline = await Note | ||||
| 		.find(query, { | ||||
| 			limit: limit, | ||||
| 			sort: sort | ||||
| 		}); | ||||
| 
 | ||||
| 	// Serialize
 | ||||
| 	return await Promise.all(timeline.map(note => pack(note, user))); | ||||
| }; | ||||
|  | @ -0,0 +1,23 @@ | |||
| import $ from 'cafy'; import ID from '../../../../../cafy-id'; | ||||
| import UserList, { pack } from '../../../../../models/user-list'; | ||||
| 
 | ||||
| /** | ||||
|  * Show a user list | ||||
|  */ | ||||
| module.exports = async (params, me) => new Promise(async (res, rej) => { | ||||
| 	// Get 'listId' parameter
 | ||||
| 	const [listId, listIdErr] = $(params.listId).type(ID).$; | ||||
| 	if (listIdErr) return rej('invalid listId param'); | ||||
| 
 | ||||
| 	// Fetch the list
 | ||||
| 	const userList = await UserList.findOne({ | ||||
| 		_id: listId, | ||||
| 		userId: me._id, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (userList == null) { | ||||
| 		return rej('list not found'); | ||||
| 	} | ||||
| 
 | ||||
| 	res(await pack(userList)); | ||||
| }); | ||||
|  | @ -1,15 +1,11 @@ | |||
| /** | ||||
|  * Module dependencies | ||||
|  */ | ||||
| import $ from 'cafy'; import ID from '../../../../cafy-id'; | ||||
| import $ from 'cafy'; | ||||
| import User, { pack } from '../../../../models/user'; | ||||
| 
 | ||||
| /** | ||||
|  * Search a user by username | ||||
|  * | ||||
|  * @param {any} params | ||||
|  * @param {any} me | ||||
|  * @return {Promise<any>} | ||||
|  */ | ||||
| module.exports = (params, me) => new Promise(async (res, rej) => { | ||||
| 	// Get 'query' parameter
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue