✌️
This commit is contained in:
		
							parent
							
								
									8a8d97b8c7
								
							
						
					
					
						commit
						4953842ff1
					
				
					 9 changed files with 165 additions and 25 deletions
				
			
		|  | @ -98,7 +98,9 @@ common/views/components/nav.vue: | ||||||
|   feedback: "Feedback" |   feedback: "Feedback" | ||||||
| 
 | 
 | ||||||
| common/views/components/note-menu.vue: | common/views/components/note-menu.vue: | ||||||
|  |   favorite: "Favorite this note" | ||||||
|   pin: "Pin to profile page" |   pin: "Pin to profile page" | ||||||
|  |   remote: "Show on origin" | ||||||
| 
 | 
 | ||||||
| common/views/components/poll.vue: | common/views/components/poll.vue: | ||||||
|   vote-to: "Vote for '{}'" |   vote-to: "Vote for '{}'" | ||||||
|  |  | ||||||
|  | @ -98,7 +98,9 @@ common/views/components/nav.vue: | ||||||
|   feedback: "フィードバック" |   feedback: "フィードバック" | ||||||
| 
 | 
 | ||||||
| common/views/components/note-menu.vue: | common/views/components/note-menu.vue: | ||||||
|  |   favorite: "Favorite this note" | ||||||
|   pin: "Épingler sur votre profile" |   pin: "Épingler sur votre profile" | ||||||
|  |   remote: "投稿元で見る" | ||||||
| 
 | 
 | ||||||
| common/views/components/poll.vue: | common/views/components/poll.vue: | ||||||
|   vote-to: "Voter pour '{}'" |   vote-to: "Voter pour '{}'" | ||||||
|  |  | ||||||
|  | @ -98,7 +98,9 @@ common/views/components/nav.vue: | ||||||
|   feedback: "フィードバック" |   feedback: "フィードバック" | ||||||
| 
 | 
 | ||||||
| common/views/components/note-menu.vue: | common/views/components/note-menu.vue: | ||||||
|  |   favorite: "お気に入り" | ||||||
|   pin: "ピン留め" |   pin: "ピン留め" | ||||||
|  |   remote: "投稿元で見る" | ||||||
| 
 | 
 | ||||||
| common/views/components/poll.vue: | common/views/components/poll.vue: | ||||||
|   vote-to: "「{}」に投票する" |   vote-to: "「{}」に投票する" | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| <div class="mk-note-menu"> | <div class="mk-note-menu"> | ||||||
| 	<div class="backdrop" ref="backdrop" @click="close"></div> | 	<div class="backdrop" ref="backdrop" @click="close"></div> | ||||||
| 	<div class="popover" :class="{ compact }" ref="popover"> | 	<div class="popover" :class="{ compact }" ref="popover"> | ||||||
|  | 		<button @click="favorite">%i18n:@favorite%</button> | ||||||
| 		<button v-if="note.userId == os.i.id" @click="pin">%i18n:@pin%</button> | 		<button v-if="note.userId == os.i.id" @click="pin">%i18n:@pin%</button> | ||||||
| 		<a v-if="note.uri" :href="note.uri" target="_blank">%i18n:@remote%</a> | 		<a v-if="note.uri" :href="note.uri" target="_blank">%i18n:@remote%</a> | ||||||
| 	</div> | 	</div> | ||||||
|  | @ -58,6 +59,14 @@ export default Vue.extend({ | ||||||
| 			}); | 			}); | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
|  | 		favorite() { | ||||||
|  | 			(this as any).api('notes/favorites/create', { | ||||||
|  | 				noteId: this.note.id | ||||||
|  | 			}).then(() => { | ||||||
|  | 				this.$destroy(); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
| 		close() { | 		close() { | ||||||
| 			(this.$refs.backdrop as any).style.pointerEvents = 'none'; | 			(this.$refs.backdrop as any).style.pointerEvents = 'none'; | ||||||
| 			anime({ | 			anime({ | ||||||
|  | @ -142,6 +151,7 @@ $border-color = rgba(27, 31, 35, 0.15) | ||||||
| 		> a | 		> a | ||||||
| 			display block | 			display block | ||||||
| 			padding 8px 16px | 			padding 8px 16px | ||||||
|  | 			width 100% | ||||||
| 
 | 
 | ||||||
| 			&:hover | 			&:hover | ||||||
| 				color $theme-color-foreground | 				color $theme-color-foreground | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ import updateBanner from './api/update-banner'; | ||||||
| 
 | 
 | ||||||
| import MkIndex from './views/pages/index.vue'; | import MkIndex from './views/pages/index.vue'; | ||||||
| import MkUser from './views/pages/user/user.vue'; | import MkUser from './views/pages/user/user.vue'; | ||||||
|  | import MkFavorites from './views/pages/favorites.vue'; | ||||||
| import MkSelectDrive from './views/pages/selectdrive.vue'; | import MkSelectDrive from './views/pages/selectdrive.vue'; | ||||||
| import MkDrive from './views/pages/drive.vue'; | import MkDrive from './views/pages/drive.vue'; | ||||||
| import MkHomeCustomize from './views/pages/home-customize.vue'; | import MkHomeCustomize from './views/pages/home-customize.vue'; | ||||||
|  | @ -50,6 +51,7 @@ init(async (launch) => { | ||||||
| 		routes: [ | 		routes: [ | ||||||
| 			{ path: '/', name: 'index', component: MkIndex }, | 			{ path: '/', name: 'index', component: MkIndex }, | ||||||
| 			{ path: '/i/customize-home', component: MkHomeCustomize }, | 			{ path: '/i/customize-home', component: MkHomeCustomize }, | ||||||
|  | 			{ path: '/i/favorites', component: MkFavorites }, | ||||||
| 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | 			{ path: '/i/messaging/:user', component: MkMessagingRoom }, | ||||||
| 			{ path: '/i/drive', component: MkDrive }, | 			{ path: '/i/drive', component: MkDrive }, | ||||||
| 			{ path: '/i/drive/folder/:folder', component: MkDrive }, | 			{ path: '/i/drive/folder/:folder', component: MkDrive }, | ||||||
|  |  | ||||||
							
								
								
									
										73
									
								
								src/client/app/desktop/views/pages/favorites.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/client/app/desktop/views/pages/favorites.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | ||||||
|  | <template> | ||||||
|  | <mk-ui> | ||||||
|  | 	<main v-if="!fetching"> | ||||||
|  | 		<template v-for="favorite in favorites"> | ||||||
|  | 			<mk-note-detail :note="favorite.note" :key="favorite.note.id"/> | ||||||
|  | 		</template> | ||||||
|  | 		<a v-if="existMore" @click="more">さらに読み込む</a> | ||||||
|  | 	</main> | ||||||
|  | </mk-ui> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import Progress from '../../../common/scripts/loading'; | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			fetching: true, | ||||||
|  | 			favorites: [], | ||||||
|  | 			existMore: false, | ||||||
|  | 			moreFetching: false | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 	created() { | ||||||
|  | 		this.fetch(); | ||||||
|  | 	}, | ||||||
|  | 	methods: { | ||||||
|  | 		fetch() { | ||||||
|  | 			Progress.start(); | ||||||
|  | 			this.fetching = true; | ||||||
|  | 
 | ||||||
|  | 			(this as any).api('i/favorites', { | ||||||
|  | 				limit: 11 | ||||||
|  | 			}).then(favorites => { | ||||||
|  | 				if (favorites.length == 11) { | ||||||
|  | 					this.existMore = true; | ||||||
|  | 					favorites.pop(); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				this.favorites = favorites; | ||||||
|  | 				this.fetching = false; | ||||||
|  | 
 | ||||||
|  | 				Progress.done(); | ||||||
|  | 			}); | ||||||
|  | 		}, | ||||||
|  | 		more() { | ||||||
|  | 			this.moreFetching = true; | ||||||
|  | 			(this as any).api('i/favorites', { | ||||||
|  | 				limit: 11, | ||||||
|  | 				maxId: this.favorites[this.favorites.length - 1].id | ||||||
|  | 			}).then(favorites => { | ||||||
|  | 				if (favorites.length == 11) { | ||||||
|  | 					this.existMore = true; | ||||||
|  | 					favorites.pop(); | ||||||
|  | 				} else { | ||||||
|  | 					this.existMore = false; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				this.favorites = this.favorites.concat(favorites); | ||||||
|  | 				this.moreFetching = false; | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | main | ||||||
|  | 	margin 0 auto | ||||||
|  | 	padding 16px | ||||||
|  | 	max-width 700px | ||||||
|  | </style> | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import * as mongo from 'mongodb'; | import * as mongo from 'mongodb'; | ||||||
|  | import deepcopy = require('deepcopy'); | ||||||
| import db from '../db/mongodb'; | import db from '../db/mongodb'; | ||||||
|  | import { pack as packNote } from './note'; | ||||||
| 
 | 
 | ||||||
| const Favorite = db.get<IFavorite>('favorites'); | const Favorite = db.get<IFavorite>('favorites'); | ||||||
| Favorite.createIndex(['userId', 'noteId'], { unique: true }); | Favorite.createIndex(['userId', 'noteId'], { unique: true }); | ||||||
|  | @ -38,3 +40,35 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori | ||||||
| 		_id: f._id | 		_id: f._id | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Pack a favorite for API response | ||||||
|  |  */ | ||||||
|  | export const pack = ( | ||||||
|  | 	favorite: any, | ||||||
|  | 	me: any | ||||||
|  | ) => new Promise<any>(async (resolve, reject) => { | ||||||
|  | 	let _favorite: any; | ||||||
|  | 
 | ||||||
|  | 	// Populate the favorite if 'favorite' is ID
 | ||||||
|  | 	if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) { | ||||||
|  | 		_favorite = await Favorite.findOne({ | ||||||
|  | 			_id: favorite | ||||||
|  | 		}); | ||||||
|  | 	} else if (typeof favorite === 'string') { | ||||||
|  | 		_favorite = await Favorite.findOne({ | ||||||
|  | 			_id: new mongo.ObjectID(favorite) | ||||||
|  | 		}); | ||||||
|  | 	} else { | ||||||
|  | 		_favorite = deepcopy(favorite); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Rename _id to id
 | ||||||
|  | 	_favorite.id = _favorite._id; | ||||||
|  | 	delete _favorite._id; | ||||||
|  | 
 | ||||||
|  | 	// Populate note
 | ||||||
|  | 	_favorite.note = await packNote(_favorite.noteId, me); | ||||||
|  | 
 | ||||||
|  | 	resolve(_favorite); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | @ -233,6 +233,12 @@ const endpoints: Endpoint[] = [ | ||||||
| 		kind: 'notification-read' | 		kind: 'notification-read' | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | 	{ | ||||||
|  | 		name: 'i/favorites', | ||||||
|  | 		withCredential: true, | ||||||
|  | 		kind: 'favorites-read' | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
| 	{ | 	{ | ||||||
| 		name: 'othello/match', | 		name: 'othello/match', | ||||||
| 		withCredential: true | 		withCredential: true | ||||||
|  |  | ||||||
|  | @ -2,43 +2,52 @@ | ||||||
|  * Module dependencies |  * Module dependencies | ||||||
|  */ |  */ | ||||||
| import $ from 'cafy'; | import $ from 'cafy'; | ||||||
| import Favorite from '../../../../models/favorite'; | import Favorite, { pack } from '../../../../models/favorite'; | ||||||
| import { pack } from '../../../../models/note'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Get followers of a user |  * Get favorited notes | ||||||
|  * |  | ||||||
|  * @param {any} params |  | ||||||
|  * @param {any} user |  | ||||||
|  * @return {Promise<any>} |  | ||||||
|  */ |  */ | ||||||
| module.exports = (params, user) => new Promise(async (res, rej) => { | module.exports = (params, user) => new Promise(async (res, rej) => { | ||||||
| 	// Get 'limit' parameter
 | 	// Get 'limit' parameter
 | ||||||
| 	const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; | 	const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; | ||||||
| 	if (limitErr) return rej('invalid limit param'); | 	if (limitErr) return rej('invalid limit param'); | ||||||
| 
 | 
 | ||||||
| 	// Get 'offset' parameter
 | 	// Get 'sinceId' parameter
 | ||||||
| 	const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; | 	const [sinceId, sinceIdErr] = $(params.sinceId).optional.id().$; | ||||||
| 	if (offsetErr) return rej('invalid offset param'); | 	if (sinceIdErr) return rej('invalid sinceId param'); | ||||||
| 
 | 
 | ||||||
| 	// Get 'sort' parameter
 | 	// Get 'untilId' parameter
 | ||||||
| 	const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; | 	const [untilId, untilIdErr] = $(params.untilId).optional.id().$; | ||||||
| 	if (sortError) return rej('invalid sort param'); | 	if (untilIdErr) return rej('invalid untilId param'); | ||||||
|  | 
 | ||||||
|  | 	// Check if both of sinceId and untilId is specified
 | ||||||
|  | 	if (sinceId && untilId) { | ||||||
|  | 		return rej('cannot set sinceId and untilId'); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const query = { | ||||||
|  | 		userId: user._id | ||||||
|  | 	} as any; | ||||||
|  | 
 | ||||||
|  | 	const sort = { | ||||||
|  | 		_id: -1 | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	if (sinceId) { | ||||||
|  | 		sort._id = 1; | ||||||
|  | 		query._id = { | ||||||
|  | 			$gt: sinceId | ||||||
|  | 		}; | ||||||
|  | 	} else if (untilId) { | ||||||
|  | 		query._id = { | ||||||
|  | 			$lt: untilId | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// Get favorites
 | 	// Get favorites
 | ||||||
| 	const favorites = await Favorite | 	const favorites = await Favorite | ||||||
| 		.find({ | 		.find(query, { limit, sort }); | ||||||
| 			userId: user._id |  | ||||||
| 		}, { |  | ||||||
| 			limit: limit, |  | ||||||
| 			skip: offset, |  | ||||||
| 			sort: { |  | ||||||
| 				_id: sort == 'asc' ? 1 : -1 |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 
 | 
 | ||||||
| 	// Serialize
 | 	// Serialize
 | ||||||
| 	res(await Promise.all(favorites.map(async favorite => | 	res(await Promise.all(favorites.map(favorite => pack(favorite, user)))); | ||||||
| 		await pack(favorite.noteId) |  | ||||||
| 	))); |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue