summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/pages/user/index.vue7
-rw-r--r--src/client/pages/user/reactions.vue81
-rw-r--r--src/models/repositories/note-reaction.ts14
-rw-r--r--src/server/api/endpoints/users/reactions.ts67
4 files changed, 167 insertions, 2 deletions
diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue
index 0ddf73d572..6811dff2db 100644
--- a/src/client/pages/user/index.vue
+++ b/src/client/pages/user/index.vue
@@ -181,6 +181,7 @@
</template>
<XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _gap"/>
<XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _gap"/>
+ <XReactions v-else-if="page === 'reactions'" :user="user" class="_gap"/>
<XClips v-else-if="page === 'clips'" :user="user" class="_gap"/>
<XPages v-else-if="page === 'pages'" :user="user" class="_gap"/>
<XGallery v-else-if="page === 'gallery'" :user="user" class="_gap"/>
@@ -223,6 +224,7 @@ export default defineComponent({
MkTab,
MkInfo,
XFollowList: defineAsyncComponent(() => import('./follow-list.vue')),
+ XReactions: defineAsyncComponent(() => import('./reactions.vue')),
XClips: defineAsyncComponent(() => import('./clips.vue')),
XPages: defineAsyncComponent(() => import('./pages.vue')),
XGallery: defineAsyncComponent(() => import('./gallery.vue')),
@@ -269,6 +271,11 @@ export default defineComponent({
icon: 'fas fa-home',
onClick: () => { this.$router.push('/@' + getAcct(this.user)); },
}, {
+ active: this.page === 'reactions',
+ title: this.$ts.reaction,
+ icon: 'fas fa-laugh',
+ onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); },
+ }, {
active: this.page === 'clips',
title: this.$ts.clips,
icon: 'fas fa-paperclip',
diff --git a/src/client/pages/user/reactions.vue b/src/client/pages/user/reactions.vue
new file mode 100644
index 0000000000..5ac7e01027
--- /dev/null
+++ b/src/client/pages/user/reactions.vue
@@ -0,0 +1,81 @@
+<template>
+<div>
+ <MkPagination :pagination="pagination" #default="{items}" ref="list">
+ <div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap afdcfbfb">
+ <div class="header">
+ <MkAvatar class="avatar" :user="user"/>
+ <MkReactionIcon class="reaction" :reaction="item.type" :custom-emojis="item.note.emojis" :no-style="true"/>
+ <MkTime :time="item.createdAt" class="createdAt"/>
+ </div>
+ <MkNote :note="item.note" @update:note="updated(note, $event)" :key="item.id"/>
+ </div>
+ </MkPagination>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import MkPagination from '@client/components/ui/pagination.vue';
+import MkNote from '@client/components/note.vue';
+import MkReactionIcon from '@client/components/reaction-icon.vue';
+
+export default defineComponent({
+ components: {
+ MkPagination,
+ MkNote,
+ MkReactionIcon,
+ },
+
+ props: {
+ user: {
+ type: Object,
+ required: true
+ },
+ },
+
+ data() {
+ return {
+ pagination: {
+ endpoint: 'users/reactions',
+ limit: 20,
+ params: {
+ userId: this.user.id,
+ }
+ },
+ };
+ },
+
+ watch: {
+ user() {
+ this.$refs.list.reload();
+ }
+ },
+});
+</script>
+
+<style lang="scss" scoped>
+.afdcfbfb {
+ > .header {
+ display: flex;
+ align-items: center;
+ padding: 8px 16px;
+ margin-bottom: 8px;
+ border-bottom: solid 2px var(--divider);
+
+ > .avatar {
+ width: 24px;
+ height: 24px;
+ margin-right: 8px;
+ }
+
+ > .reaction {
+ width: 32px;
+ height: 32px;
+ }
+
+ > .createdAt {
+ margin-left: auto;
+ }
+ }
+}
+</style>
diff --git a/src/models/repositories/note-reaction.ts b/src/models/repositories/note-reaction.ts
index ba74076f6c..5d86065526 100644
--- a/src/models/repositories/note-reaction.ts
+++ b/src/models/repositories/note-reaction.ts
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from 'typeorm';
import { NoteReaction } from '@/models/entities/note-reaction';
-import { Users } from '../index';
+import { Notes, Users } from '../index';
import { Packed } from '@/misc/schema';
import { convertLegacyReaction } from '@/misc/reaction-lib';
import { User } from '@/models/entities/user';
@@ -9,8 +9,15 @@ import { User } from '@/models/entities/user';
export class NoteReactionRepository extends Repository<NoteReaction> {
public async pack(
src: NoteReaction['id'] | NoteReaction,
- me?: { id: User['id'] } | null | undefined
+ me?: { id: User['id'] } | null | undefined,
+ options?: {
+ withNote: boolean;
+ },
): Promise<Packed<'NoteReaction'>> {
+ const opts = Object.assign({
+ withNote: false,
+ }, options);
+
const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src);
return {
@@ -18,6 +25,9 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
createdAt: reaction.createdAt.toISOString(),
user: await Users.pack(reaction.userId, me),
type: convertLegacyReaction(reaction.reaction),
+ ...(opts.withNote ? {
+ note: await Notes.pack(reaction.noteId, me),
+ } : {})
};
}
}
diff --git a/src/server/api/endpoints/users/reactions.ts b/src/server/api/endpoints/users/reactions.ts
new file mode 100644
index 0000000000..44d7887482
--- /dev/null
+++ b/src/server/api/endpoints/users/reactions.ts
@@ -0,0 +1,67 @@
+import $ from 'cafy';
+import { ID } from '@/misc/cafy-id';
+import define from '../../define';
+import { NoteReactions } from '@/models/index';
+import { makePaginationQuery } from '../../common/make-pagination-query';
+import { generateVisibilityQuery } from '../../common/generate-visibility-query';
+
+export const meta = {
+ tags: ['users', 'reactions'],
+
+ requireCredential: false as const,
+
+ params: {
+ userId: {
+ validator: $.type(ID),
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10,
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+
+ sinceDate: {
+ validator: $.optional.num,
+ },
+
+ untilDate: {
+ validator: $.optional.num,
+ },
+ },
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'NoteReaction',
+ }
+ },
+
+ errors: {
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'),
+ ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+ .andWhere(`reaction.userId = :userId`, { userId: ps.userId })
+ .leftJoinAndSelect('reaction.note', 'note');
+
+ generateVisibilityQuery(query, me);
+
+ const reactions = await query
+ .take(ps.limit!)
+ .getMany();
+
+ return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
+});