diff --git a/src/client/pages/user/clips.vue b/src/client/pages/user/clips.vue new file mode 100644 index 0000000000..2a66b6752d --- /dev/null +++ b/src/client/pages/user/clips.vue @@ -0,0 +1,57 @@ +<template> +<div> + <MkPagination :pagination="pagination" #default="{items}" ref="list"> + <MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _vMargin"> + <b>{{ item.name }}</b> + <div v-if="item.description" class="description">{{ item.description }}</div> + </MkA> + </MkPagination> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { userPage, acct } from '../../filters/user'; + +export default defineComponent({ + components: { + MkPagination, + }, + + props: { + user: { + type: Object, + required: true + }, + }, + + data() { + return { + pagination: { + endpoint: 'users/clips', + limit: 20, + params: { + userId: this.user.id, + } + }, + }; + }, + + watch: { + user() { + this.$refs.list.reload(); + } + }, + + methods: { + userPage, + + acct + } +}); +</script> + +<style lang="scss" scoped> + +</style> diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue index 0c816a2d22..70f6662604 100644 --- a/src/client/pages/user/index.vue +++ b/src/client/pages/user/index.vue @@ -60,12 +60,15 @@ <div class="main"> <div class="nav _vMargin"> <MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> + <Fa :icon="faCommentAlt" class="icon"/> <span>{{ $t('notes') }}</span> </MkA> <MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link"> + <Fa :icon="faPaperclip" class="icon"/> <span>{{ $t('clips') }}</span> </MkA> <MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link"> + <Fa :icon="faFileAlt" class="icon"/> <span>{{ $t('pages') }}</span> </MkA> <div class="actions"> @@ -83,6 +86,8 @@ </template> <XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_vMargin"/> <XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_vMargin"/> + <XClips v-else-if="page === 'clips'" :user="user" class="_vMargin"/> + <XPages v-else-if="page === 'pages'" :user="user" class="_vMargin"/> </div> </div> </div> @@ -193,8 +198,8 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent, computed } from 'vue'; -import { faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker } from '@fortawesome/free-solid-svg-icons'; -import { faCalendarAlt, faBookmark as farBookmark } from '@fortawesome/free-regular-svg-icons'; +import { faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faPaperclip, faFileAlt } from '@fortawesome/free-solid-svg-icons'; +import { faCalendarAlt, faBookmark as farBookmark, faCommentAlt } from '@fortawesome/free-regular-svg-icons'; import * as age from 's-age'; import XUserTimeline from './index.timeline.vue'; import XNote from '@/components/note.vue'; @@ -221,6 +226,8 @@ export default defineComponent({ MkFolder, MkTab, XFollowList: defineAsyncComponent(() => import('./follow-list.vue')), + XClips: defineAsyncComponent(() => import('./clips.vue')), + XPages: defineAsyncComponent(() => import('./pages.vue')), XPhotos: defineAsyncComponent(() => import('./index.photos.vue')), XActivity: defineAsyncComponent(() => import('./index.activity.vue')), }, @@ -251,7 +258,7 @@ export default defineComponent({ error: null, parallaxAnimationId: null, narrow: null, - faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt + faExclamationTriangle, faEllipsisH, faRobot, faLock, faBookmark, farBookmark, faChartBar, faImage, faBirthdayCake, faMapMarker, faCalendarAlt, faCommentAlt, faPaperclip, faFileAlt, }; }, @@ -471,7 +478,8 @@ export default defineComponent({ display: flex; align-items: center; margin-top: var(--margin); - font-size: 120%; + //font-size: 120%; + font-weight: bold; > .link { display: inline-block; @@ -479,6 +487,10 @@ export default defineComponent({ text-align: center; border-bottom: solid 3px transparent; + &:hover { + text-decoration: none; + } + &.active { color: var(--accent); border-bottom-color: var(--accent); diff --git a/src/client/pages/user/pages.vue b/src/client/pages/user/pages.vue new file mode 100644 index 0000000000..66658ac57d --- /dev/null +++ b/src/client/pages/user/pages.vue @@ -0,0 +1,56 @@ +<template> +<div> + <MkPagination :pagination="pagination" #default="{items}" ref="list"> + <MkPagePreview v-for="page in items" :page="page" :key="page.id" class="_vMargin"/> + </MkPagination> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import MkPagePreview from '@/components/page-preview.vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import { userPage, acct } from '../../filters/user'; + +export default defineComponent({ + components: { + MkPagination, + MkPagePreview, + }, + + props: { + user: { + type: Object, + required: true + }, + }, + + data() { + return { + pagination: { + endpoint: 'users/pages', + limit: 20, + params: { + userId: this.user.id, + } + }, + }; + }, + + watch: { + user() { + this.$refs.list.reload(); + } + }, + + methods: { + userPage, + + acct + } +}); +</script> + +<style lang="scss" scoped> + +</style> diff --git a/src/models/repositories/clip.ts b/src/models/repositories/clip.ts index 2830546528..11f743349f 100644 --- a/src/models/repositories/clip.ts +++ b/src/models/repositories/clip.ts @@ -24,6 +24,12 @@ export class ClipRepository extends Repository<Clip> { isPublic: clip.isPublic, }); } + + public packMany( + clips: Clip[], + ) { + return Promise.all(clips.map(x => this.pack(x))); + } } export const packedClipSchema = { diff --git a/src/server/api/endpoints/users/clips.ts b/src/server/api/endpoints/users/clips.ts new file mode 100644 index 0000000000..72aae7252c --- /dev/null +++ b/src/server/api/endpoints/users/clips.ts @@ -0,0 +1,40 @@ +import $ from 'cafy'; +import { ID } from '../../../../misc/cafy-id'; +import define from '../../define'; +import { Clips } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + tags: ['users', 'clips'], + + params: { + userId: { + validator: $.type(ID), + }, + + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId) + .andWhere(`clip.userId = :userId`, { userId: ps.userId }) + .andWhere('clip.isPublic = true'); + + const clips = await query + .take(ps.limit!) + .getMany(); + + return await Clips.packMany(clips); +}); diff --git a/src/server/api/endpoints/users/pages.ts b/src/server/api/endpoints/users/pages.ts new file mode 100644 index 0000000000..706a2e115b --- /dev/null +++ b/src/server/api/endpoints/users/pages.ts @@ -0,0 +1,40 @@ +import $ from 'cafy'; +import { ID } from '../../../../misc/cafy-id'; +import define from '../../define'; +import { Pages } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + tags: ['users', 'pages'], + + params: { + userId: { + validator: $.type(ID), + }, + + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) + .andWhere(`page.userId = :userId`, { userId: ps.userId }) + .andWhere('page.visibility = \'public\''); + + const pages = await query + .take(ps.limit!) + .getMany(); + + return await Pages.packMany(pages); +});