Merge c6555c2cf2
into d4654dd7bd
This commit is contained in:
commit
81d774ad10
|
@ -38,6 +38,7 @@ export type SearchOpts = {
|
|||
userId?: MiNote['userId'] | null;
|
||||
channelId?: MiNote['channelId'] | null;
|
||||
host?: string | null;
|
||||
searchFrom? : string | null;
|
||||
};
|
||||
|
||||
export type SearchPagination = {
|
||||
|
@ -184,6 +185,7 @@ export class SearchService {
|
|||
case 'sqlPgroonga': {
|
||||
// ほとんど内容に差がないのでsqlLikeとsqlPgroongaを同じ処理にしている.
|
||||
// 今後の拡張で差が出る用であれば関数を分ける.
|
||||
|
||||
return this.searchNoteByLike(q, me, opts, pagination);
|
||||
}
|
||||
case 'meilisearch': {
|
||||
|
@ -220,10 +222,27 @@ export class SearchService {
|
|||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||
|
||||
if (this.config.fulltextSearch?.provider === 'sqlPgroonga') {
|
||||
query.andWhere('note.text &@~ :q', { q });
|
||||
// pgroonga
|
||||
if(opts.searchFrom === 'textWithCw'){
|
||||
// textWithCwオプション
|
||||
query.andWhere('(coalesce(note.cw, \'\') || note.text) &@~ :q', { q });
|
||||
}else {
|
||||
// 通常検索
|
||||
query.andWhere('note.text &@~ :q', { q });
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
|
||||
}
|
||||
// Postgresql 標準
|
||||
if(opts.searchFrom === 'textWithCw'){
|
||||
// textWithCwオプション
|
||||
query.andWhere('LOWER((coalesce(note.cw, \'\') || note.text)) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
|
||||
}else {
|
||||
// 通常検索
|
||||
query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (opts.host) {
|
||||
if (opts.host === '.') {
|
||||
|
|
|
@ -51,6 +51,7 @@ export const paramDef = {
|
|||
},
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||
searchFrom: { type: 'string', nullable: true, default: null },
|
||||
},
|
||||
required: ['query'],
|
||||
} as const;
|
||||
|
@ -78,6 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userId: ps.userId,
|
||||
channelId: ps.channelId,
|
||||
host: ps.host,
|
||||
searchFrom: ps.searchFrom
|
||||
}, {
|
||||
untilId: untilId,
|
||||
sinceId: sinceId,
|
||||
|
|
|
@ -15,8 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<template #prefix><i class="ti ti-search"></i></template>
|
||||
</MkInput>
|
||||
<MkFoldableSection expanded>
|
||||
<template #header>{{ i18n.ts.options }}</template>
|
||||
<div >
|
||||
<div >{{ i18n.ts.options }}</div>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkRadios v-model="searchScope">
|
||||
|
@ -26,6 +26,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option value="user">{{ i18n.ts._search.searchScopeUser }}</option>
|
||||
</MkRadios>
|
||||
|
||||
<MkRadios v-model="searchFrom">
|
||||
<option value="text">本文</option>
|
||||
<option value="textWithCw">本文+CW</option>
|
||||
</MkRadios>
|
||||
|
||||
<div v-if="instance.federation !== 'none' && searchScope === 'server'" :class="$style.subOptionRoot">
|
||||
<MkInput
|
||||
v-model="hostInput"
|
||||
|
@ -87,31 +92,37 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkFoldableSection>
|
||||
</div>
|
||||
<div>
|
||||
<MkButton
|
||||
large
|
||||
primary
|
||||
gradate
|
||||
rounded
|
||||
:disabled="searchParams == null"
|
||||
:disabled="
|
||||
searchParams == null
|
||||
|| !(componentBlockSearchUntil && now? componentBlockSearchUntil < now : true) // disableの条件なので逆をとる
|
||||
"
|
||||
style="margin: 0 auto;"
|
||||
|
||||
@click="search"
|
||||
>
|
||||
{{ i18n.ts.search }}
|
||||
<MkTime v-if="componentBlockSearchUntil && now && componentBlockSearchUntil > now" :time="componentBlockSearchUntil" />
|
||||
</MkButton>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkFoldableSection v-if="paginator">
|
||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||
<div v-if="paginator">
|
||||
<div >{{ i18n.ts.searchResult }}</div>
|
||||
<MkNotesTimeline :key="`searchNotes:${key}`" :paginator="paginator"/>
|
||||
</MkFoldableSection>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, markRaw, ref, shallowRef, toRef } from 'vue';
|
||||
import { computed, markRaw, ref, shallowRef, toRef} from 'vue';
|
||||
import { host as localHost } from '@@/js/config.js';
|
||||
import type * as Misskey from 'misskey-js';
|
||||
import { $i } from '@/i.js';
|
||||
|
@ -122,7 +133,6 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
|||
import { apLookup } from '@/utility/lookup.js';
|
||||
import { useRouter } from '@/router.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
|
@ -179,15 +189,17 @@ if (fetchedUser != null) {
|
|||
|
||||
const searchScope = ref<'all' | 'local' | 'server' | 'user'>((() => {
|
||||
if (user.value != null) return 'user';
|
||||
if (noteSearchableScope === 'local') return 'local';
|
||||
if (hostInput.value) return 'server';
|
||||
return 'all';
|
||||
return 'local';
|
||||
})());
|
||||
|
||||
const searchFrom = ref<'text' | 'textWithCw'>('textWithCw');
|
||||
|
||||
type SearchParams = {
|
||||
readonly query: string;
|
||||
readonly host?: string;
|
||||
readonly userId?: string;
|
||||
readonly searchFrom?: string;
|
||||
};
|
||||
|
||||
const fixHostIfLocal = (target: string | null | undefined) => {
|
||||
|
@ -205,6 +217,7 @@ const searchParams = computed<SearchParams | null>(() => {
|
|||
query: trimmedQuery,
|
||||
host: fixHostIfLocal(user.value.host),
|
||||
userId: user.value.id,
|
||||
searchFrom: searchFrom.value,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -219,6 +232,7 @@ const searchParams = computed<SearchParams | null>(() => {
|
|||
return {
|
||||
query: trimmedQuery,
|
||||
host: fixHostIfLocal(trimmedHost),
|
||||
searchFrom: searchFrom.value
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -226,11 +240,13 @@ const searchParams = computed<SearchParams | null>(() => {
|
|||
return {
|
||||
query: trimmedQuery,
|
||||
host: '.',
|
||||
searchFrom: searchFrom.value
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
query: trimmedQuery,
|
||||
searchFrom: searchFrom.value
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -251,9 +267,28 @@ function removeUser() {
|
|||
user.value = null;
|
||||
}
|
||||
|
||||
const localBlockSearchUntil = localStorage.getItem('noteSearchedAt')
|
||||
const componentBlockSearchUntil = ref(localBlockSearchUntil ? new Date(parseInt(localBlockSearchUntil)): null)
|
||||
const now = ref<Date | null >(new Date())
|
||||
|
||||
|
||||
async function search() {
|
||||
if (searchParams.value == null) return;
|
||||
|
||||
// バブリング防止チェック
|
||||
const noteSearchedAt = localStorage.getItem('noteSearchedAt');
|
||||
const now = Date.now();
|
||||
if (noteSearchedAt && now < parseInt(noteSearchedAt)) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('noteSearchedAt', now.toString());
|
||||
componentBlockSearchUntil.value = new Date(now + 6 * 1000)
|
||||
console.log(componentBlockSearchUntil.value)
|
||||
setTimeout(() => {
|
||||
localStorage.removeItem('noteSearchedAt')
|
||||
componentBlockSearchUntil.value = null
|
||||
}, 6 * 1000)
|
||||
|
||||
//#region AP lookup
|
||||
if (searchParams.value.query.startsWith('https://') && !searchParams.value.query.includes(' ')) {
|
||||
const confirm = await os.confirm({
|
||||
|
|
Loading…
Reference in New Issue