diff options
| author | たーびん <tar.bin.master@gmail.com> | 2023-04-11 07:42:27 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-11 07:42:27 +0900 |
| commit | f6dc100748ef403be1fdde8dd28b62ab4e11c5b3 (patch) | |
| tree | 8444e2c47386af0cc5761fb69da096690a7438b8 | |
| parent | カスタム絵文字のキャッシュ時に"{}"が入ってしまう問題... (diff) | |
| download | sharkey-f6dc100748ef403be1fdde8dd28b62ab4e11c5b3.tar.gz sharkey-f6dc100748ef403be1fdde8dd28b62ab4e11c5b3.tar.bz2 sharkey-f6dc100748ef403be1fdde8dd28b62ab4e11c5b3.zip | |
fix #10554 チャンネルの検索用ページとAPIの追加 (#10555)
* add channel search
* move channel search to channel list page
---------
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: atsuchan <83960488+atsu1125@users.noreply.github.com>
Co-authored-by: Masaya Suzuki <15100604+massongit@users.noreply.github.com>
Co-authored-by: Kagami Sascha Rosylight <saschanaz@outlook.com>
Co-authored-by: taiy <53635909+taiyme@users.noreply.github.com>
Co-authored-by: xianon <xianon@hotmail.co.jp>
Co-authored-by: kabo2468 <28654659+kabo2468@users.noreply.github.com>
Co-authored-by: YS <47836716+yszkst@users.noreply.github.com>
Co-authored-by: Khsmty <me@khsmty.com>
Co-authored-by: Soni L <EnderMoneyMod@gmail.com>
Co-authored-by: mei23 <m@m544.net>
Co-authored-by: daima3629 <52790780+daima3629@users.noreply.github.com>
Co-authored-by: Windymelt <1113940+windymelt@users.noreply.github.com>
| -rw-r--r-- | locales/ja-JP.yml | 2 | ||||
| -rw-r--r-- | packages/backend/src/server/api/EndpointsModule.ts | 4 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints.ts | 2 | ||||
| -rw-r--r-- | packages/backend/src/server/api/endpoints/channels/search.ts | 67 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkChannelList.vue | 31 | ||||
| -rw-r--r-- | packages/frontend/src/pages/channels.vue | 62 |
6 files changed, 166 insertions, 2 deletions
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b01699cab4..bdf2cada86 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1427,6 +1427,8 @@ _channel: following: "フォロー中" usersCount: "{n}人が参加中" notesCount: "{n}投稿があります" + nameAndDescription: "名前と説明" + nameOnly: "名前のみ" _menuDisplay: sideFull: "横" diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 5a53b3faf7..ca89d82853 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -98,6 +98,7 @@ import * as ep___channels_update from './endpoints/channels/update.js'; import * as ep___channels_favorite from './endpoints/channels/favorite.js'; import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js'; import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js'; +import * as ep___channels_search from './endpoints/channels/search.js'; import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; import * as ep___charts_drive from './endpoints/charts/drive.js'; @@ -431,6 +432,7 @@ const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default }; const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default }; const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default }; +const $channels_search: Provider = { provide: 'ep:channels/search', useClass: ep___channels_search.default }; const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default }; const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default }; const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default }; @@ -768,6 +770,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $channels_favorite, $channels_unfavorite, $channels_myFavorites, + $channels_search, $charts_activeUsers, $charts_apRequest, $charts_drive, @@ -1099,6 +1102,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $channels_favorite, $channels_unfavorite, $channels_myFavorites, + $channels_search, $charts_activeUsers, $charts_apRequest, $charts_drive, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index fd268c7912..dab897117d 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -98,6 +98,7 @@ import * as ep___channels_update from './endpoints/channels/update.js'; import * as ep___channels_favorite from './endpoints/channels/favorite.js'; import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js'; import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js'; +import * as ep___channels_search from './endpoints/channels/search.js'; import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; import * as ep___charts_drive from './endpoints/charts/drive.js'; @@ -429,6 +430,7 @@ const eps = [ ['channels/favorite', ep___channels_favorite], ['channels/unfavorite', ep___channels_unfavorite], ['channels/my-favorites', ep___channels_myFavorites], + ['channels/search', ep___channels_search], ['charts/active-users', ep___charts_activeUsers], ['charts/ap-request', ep___charts_apRequest], ['charts/drive', ep___charts_drive], diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts new file mode 100644 index 0000000000..a954ba224c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/channels/search.ts @@ -0,0 +1,67 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueryService } from '@/core/QueryService.js'; +import type { ChannelsRepository } from '@/models/index.js'; +import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; + +export const meta = { + tags: ['channels'], + + requireCredential: false, + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Channel', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + query: { type: 'string' }, + type: { type: 'string', enum: ['nameAndDescription', 'nameOnly'], default: 'nameAndDescription' }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 }, + }, + required: ['query'], +} as const; + +// eslint-disable-next-line import/no-default-export +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { + constructor( + @Inject(DI.channelsRepository) + private channelsRepository: ChannelsRepository, + + private channelEntityService: ChannelEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId); + + if (ps.type === 'nameAndDescription') { + query.andWhere(new Brackets(qb => { qb + .where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }) + .orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + })); + } else { + query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` }); + } + + const channels = await query + .take(ps.limit) + .getMany(); + + return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me))); + }); + } +} diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue new file mode 100644 index 0000000000..408eab7399 --- /dev/null +++ b/packages/frontend/src/components/MkChannelList.vue @@ -0,0 +1,31 @@ +<template> +<MkPagination :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> + <div>{{ i18n.ts.notFound }}</div> + </div> + </template> + + <template #default="{ items }"> + <MkChannelPreview v-for="item in items" :key="item.id" class="_margin" :channel="extractor(item)"/> + </template> +</MkPagination> +</template> + +<script lang="ts" setup> +import MkChannelPreview from '@/components/MkChannelPreview.vue'; +import MkPagination, { Paging } from '@/components/MkPagination.vue'; +import { i18n } from '@/i18n'; + +const props = withDefaults(defineProps<{ + pagination: Paging; + noGap?: boolean; + extractor?: (item: any) => any; +}>(), { + extractor: (item) => item, +}); +</script> + +<style lang="scss" scoped> +</style> diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue index 3a5aa00c5b..bc6a6e4952 100644 --- a/packages/frontend/src/pages/channels.vue +++ b/packages/frontend/src/pages/channels.vue @@ -2,6 +2,23 @@ <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :content-max="700"> + <div v-if="tab === 'search'"> + <div class="_gaps"> + <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search"> + <template #prefix><i class="ti ti-search"></i></template> + </MkInput> + <MkRadios v-model="searchType" @update:model-value="search()"> + <option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option> + <option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option> + </MkRadios> + <MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton> + </div> + + <MkFoldableSection v-if="channelPagination"> + <template #header>{{ i18n.ts.searchResult }}</template> + <MkChannelList :key="key" :pagination="channelPagination"/> + </MkFoldableSection> + </div> <div v-if="tab === 'featured'"> <MkPagination v-slot="{items}" :pagination="featuredPagination"> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/> @@ -28,17 +45,35 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, onMounted } from 'vue'; import MkChannelPreview from '@/components/MkChannelPreview.vue'; +import MkChannelList from '@/components/MkChannelList.vue'; import MkPagination from '@/components/MkPagination.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; +import MkFoldableSection from '@/components/MkFoldableSection.vue'; import { useRouter } from '@/router'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; const router = useRouter(); -let tab = $ref('featured'); +const props = defineProps<{ + query: string; + type?: string; +}>(); + +let key = $ref(''); +let tab = $ref('search'); +let searchQuery = $ref(''); +let searchType = $ref('nameAndDescription'); +let channelPagination = $ref(); + +onMounted(() => { + searchQuery = props.query ?? ''; + searchType = props.type ?? 'nameAndDescription'; +}); const featuredPagination = { endpoint: 'channels/featured' as const, @@ -58,6 +93,25 @@ const ownedPagination = { limit: 10, }; +async function search() { + const query = searchQuery.toString().trim(); + + if (query == null || query === '') return; + + const type = searchType.toString().trim(); + + channelPagination = { + endpoint: 'channels/search', + limit: 10, + params: { + query: searchQuery, + type: type, + }, + }; + + key = query + type; +} + function create() { router.push('/channels/new'); } @@ -69,6 +123,10 @@ const headerActions = $computed(() => [{ }]); const headerTabs = $computed(() => [{ + key: 'search', + title: i18n.ts.search, + icon: 'ti ti-search', +}, { key: 'featured', title: i18n.ts._channel.featured, icon: 'ti ti-comet', |