summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorたーびん <tar.bin.master@gmail.com>2023-04-11 07:42:27 +0900
committerGitHub <noreply@github.com>2023-04-11 07:42:27 +0900
commitf6dc100748ef403be1fdde8dd28b62ab4e11c5b3 (patch)
tree8444e2c47386af0cc5761fb69da096690a7438b8
parentカスタム絵文字のキャッシュ時に"{}"が入ってしまう問題... (diff)
downloadsharkey-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.yml2
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts4
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/channels/search.ts67
-rw-r--r--packages/frontend/src/components/MkChannelList.vue31
-rw-r--r--packages/frontend/src/pages/channels.vue62
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',