From 4953842ff1c65fc850c856e11598e9901ff7c6e1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 20 Apr 2018 13:31:43 +0900 Subject: [PATCH] :v: --- locales/en.yml | 2 + locales/fr.yml | 2 + locales/ja.yml | 2 + .../app/common/views/components/note-menu.vue | 10 +++ src/client/app/desktop/script.ts | 2 + .../app/desktop/views/pages/favorites.vue | 73 +++++++++++++++++++ src/models/favorite.ts | 34 +++++++++ src/server/api/endpoints.ts | 6 ++ src/server/api/endpoints/i/favorites.ts | 59 ++++++++------- 9 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 src/client/app/desktop/views/pages/favorites.vue diff --git a/locales/en.yml b/locales/en.yml index db7ad786b..0a0539322 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -98,7 +98,9 @@ common/views/components/nav.vue: feedback: "Feedback" common/views/components/note-menu.vue: + favorite: "Favorite this note" pin: "Pin to profile page" + remote: "Show on origin" common/views/components/poll.vue: vote-to: "Vote for '{}'" diff --git a/locales/fr.yml b/locales/fr.yml index 09e5b3878..e640c4883 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -98,7 +98,9 @@ common/views/components/nav.vue: feedback: "フィードバック" common/views/components/note-menu.vue: + favorite: "Favorite this note" pin: "Épingler sur votre profile" + remote: "投稿元で見る" common/views/components/poll.vue: vote-to: "Voter pour '{}'" diff --git a/locales/ja.yml b/locales/ja.yml index 2401bf1fc..3d023281c 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -98,7 +98,9 @@ common/views/components/nav.vue: feedback: "フィードバック" common/views/components/note-menu.vue: + favorite: "お気に入り" pin: "ピン留め" + remote: "投稿元で見る" common/views/components/poll.vue: vote-to: "「{}」に投票する" diff --git a/src/client/app/common/views/components/note-menu.vue b/src/client/app/common/views/components/note-menu.vue index 877d2c16b..3e4be425d 100644 --- a/src/client/app/common/views/components/note-menu.vue +++ b/src/client/app/common/views/components/note-menu.vue @@ -2,6 +2,7 @@
+ %i18n:@remote%
@@ -58,6 +59,14 @@ export default Vue.extend({ }); }, + favorite() { + (this as any).api('notes/favorites/create', { + noteId: this.note.id + }).then(() => { + this.$destroy(); + }); + }, + close() { (this.$refs.backdrop as any).style.pointerEvents = 'none'; anime({ @@ -142,6 +151,7 @@ $border-color = rgba(27, 31, 35, 0.15) > a display block padding 8px 16px + width 100% &:hover color $theme-color-foreground diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts index 8d95d8177..3b0ed48cd 100644 --- a/src/client/app/desktop/script.ts +++ b/src/client/app/desktop/script.ts @@ -25,6 +25,7 @@ import updateBanner from './api/update-banner'; import MkIndex from './views/pages/index.vue'; 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 MkHomeCustomize from './views/pages/home-customize.vue'; @@ -50,6 +51,7 @@ init(async (launch) => { routes: [ { path: '/', name: 'index', component: MkIndex }, { path: '/i/customize-home', component: MkHomeCustomize }, + { path: '/i/favorites', component: MkFavorites }, { path: '/i/messaging/:user', component: MkMessagingRoom }, { path: '/i/drive', component: MkDrive }, { path: '/i/drive/folder/:folder', component: MkDrive }, diff --git a/src/client/app/desktop/views/pages/favorites.vue b/src/client/app/desktop/views/pages/favorites.vue new file mode 100644 index 000000000..d908c08f7 --- /dev/null +++ b/src/client/app/desktop/views/pages/favorites.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/models/favorite.ts b/src/models/favorite.ts index 5387b2945..d24833f19 100644 --- a/src/models/favorite.ts +++ b/src/models/favorite.ts @@ -1,5 +1,7 @@ import * as mongo from 'mongodb'; +import deepcopy = require('deepcopy'); import db from '../db/mongodb'; +import { pack as packNote } from './note'; const Favorite = db.get('favorites'); Favorite.createIndex(['userId', 'noteId'], { unique: true }); @@ -38,3 +40,35 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori _id: f._id }); } + +/** + * Pack a favorite for API response + */ +export const pack = ( + favorite: any, + me: any +) => new Promise(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); +}); diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index 7cf49debe..368691814 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -233,6 +233,12 @@ const endpoints: Endpoint[] = [ kind: 'notification-read' }, + { + name: 'i/favorites', + withCredential: true, + kind: 'favorites-read' + }, + { name: 'othello/match', withCredential: true diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index b40f2b388..f390ef9ec 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -2,43 +2,52 @@ * Module dependencies */ import $ from 'cafy'; -import Favorite from '../../../../models/favorite'; -import { pack } from '../../../../models/note'; +import Favorite, { pack } from '../../../../models/favorite'; /** - * Get followers of a user - * - * @param {any} params - * @param {any} user - * @return {Promise} + * Get favorited notes */ module.exports = (params, user) => new Promise(async (res, rej) => { // Get 'limit' parameter const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; if (limitErr) return rej('invalid limit param'); - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); + // Get 'sinceId' parameter + const [sinceId, sinceIdErr] = $(params.sinceId).optional.id().$; + if (sinceIdErr) return rej('invalid sinceId param'); - // Get 'sort' parameter - const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; - if (sortError) return rej('invalid sort param'); + // Get 'untilId' parameter + const [untilId, untilIdErr] = $(params.untilId).optional.id().$; + 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 const favorites = await Favorite - .find({ - userId: user._id - }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); + .find(query, { limit, sort }); // Serialize - res(await Promise.all(favorites.map(async favorite => - await pack(favorite.noteId) - ))); + res(await Promise.all(favorites.map(favorite => pack(favorite, user)))); });