This commit is contained in:
syuilo 2018-04-20 13:31:43 +09:00
parent 8a8d97b8c7
commit 4953842ff1
9 changed files with 165 additions and 25 deletions

View File

@ -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 '{}'"

View File

@ -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 '{}'"

View File

@ -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: "「{}」に投票する"

View File

@ -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

View File

@ -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 },

View 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>

View File

@ -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);
});

View File

@ -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

View File

@ -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)
)));
}); });