Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

検索からハッシュタグのページが開けるように、users/searchに@から始まる文字列が与えられた際の処理を修正 等 #13858

Merged
merged 44 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
04c0e08
enhance(frontend): 検索からハッシュタグのページを開けるように
Sayamame-beans May 21, 2024
f54f039
fix(frontend): 照会で入力が`#`のみの場合は`/tags/`に遷移しないように
Sayamame-beans May 21, 2024
c5d937e
docs(changelog): update changelog
Sayamame-beans May 21, 2024
16021ff
enhance(frontend): ユーザー検索からもハッシュタグのページを開けるように
Sayamame-beans May 21, 2024
6ed48fc
docs(changelog): update changelog
Sayamame-beans May 21, 2024
a1513dd
Merge remote-tracking branch 'upstream/develop' into forward_hashtag_…
Sayamame-beans May 25, 2024
f84eece
enhance(frontend): 検索範囲等が指定されている時は照会/ハッシュタグページを開かないように
Sayamame-beans May 25, 2024
4d3c232
enhance(frontend): 検索内容に空白が含まれている場合は照会/ハッシュタグページを開かないように
Sayamame-beans May 25, 2024
4ddb831
docs(changelog): update changelog
Sayamame-beans May 25, 2024
423fb62
Revert "enhance(frontend): 検索範囲等が指定されている時は照会/ハッシュタグページを開かないように"
Sayamame-beans May 25, 2024
cbfbc27
enhance(frontend): 検索から照会/ハッシュタグページを開くかどうか確認するように
Sayamame-beans May 25, 2024
cc86406
docs(changelog): update changelog
Sayamame-beans May 25, 2024
452ab50
chore: fix lint
Sayamame-beans May 25, 2024
775b7a7
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans May 27, 2024
72bb812
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans May 31, 2024
3f4853e
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jun 22, 2024
c60af5a
docs(changelog): update changelog insertion position
Sayamame-beans Jun 22, 2024
9f847c3
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jun 25, 2024
92c5851
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jun 30, 2024
d05ca33
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 3, 2024
0d1ef09
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 5, 2024
e0d23b5
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 7, 2024
3e60af8
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 14, 2024
32f3d9f
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 15, 2024
b4e8b46
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 17, 2024
6fe469d
enhance(frontend): 検索から`@user@host`の形式で照会出来るように
Sayamame-beans Jul 17, 2024
ca357a1
fix(frontend): 照会で入力が`@`のみの場合に`/@`に遷移しないように
Sayamame-beans Jul 17, 2024
fe78f61
fix(backend): `users/search`において`@`から始まるqueryに対する処理が正しくなかった問題を修正
Sayamame-beans Jul 17, 2024
49295f0
docs(changelog): update changelog
Sayamame-beans Jul 17, 2024
61af2bb
chore(backend): fix lint error
Sayamame-beans Jul 17, 2024
267aec2
fix(backend): more improvements for users/search when query startswit…
Sayamame-beans Jul 17, 2024
7c304f4
chore: unify common conditions
Sayamame-beans Jul 17, 2024
85a4d07
docs(changelog): refine changelog
Sayamame-beans Jul 17, 2024
031c6f5
chore(backend): fix lint error
Sayamame-beans Jul 17, 2024
adbc1ec
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 17, 2024
0b95888
MkInputをpreventに対応させ、enterの意図せぬ伝搬を防ぐ
samunohito Jul 22, 2024
e3c5fb9
chore(frontend/search.user): use .prevent to prevent the propagation …
Sayamame-beans Jul 24, 2024
8d6ad56
Merge pull request #1 from samunohito/forward_hashtag_search_fix
Sayamame-beans Jul 24, 2024
057fcd9
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 24, 2024
726058d
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 25, 2024
668d566
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 27, 2024
1c7edd8
Merge branch 'develop' into forward_hashtag_search
Sayamame-beans Jul 28, 2024
86289cc
Merge branch 'develop' into forward_hashtag_search
syuilo Jul 28, 2024
76bb470
Merge branch develop into forward_hashtag_search
tai-cha Jul 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
- Enhance: AiScriptを0.19.0にアップデート
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
- Enhance: センシティブなメディアを開く際に確認ダイアログを出せるように
- Enhance: 検索(ノート/ユーザー)で `#` から始まる文字列を入力すると、そのハッシュタグのノート/ユーザー一覧ページが表示できるように
- Enhance: 検索(ノート/ユーザー)において、入力に空白が含まれている場合は照会を行わないように
- Enhance: 検索(ノート/ユーザー)において、照会を行うかどうか、ハッシュタグのノート/ユーザー一覧ページを表示するかどうかの確認ダイアログを出すように
- Enhance: 検索(ノート/ユーザー)で `@` から始まる文字列(`@user@host`など)を入力すると、そのユーザーを照会できるように
- Enhance: ドライブのファイル・フォルダをドラッグしなくても移動できるように
(Cherry-picked from https://github.com/nafu-at/misskey/commit/b89c2af6945c6a9f9f10e83f54d2bcf0f240b0b4, https://github.com/nafu-at/misskey/commit/8a7d710c6acb83f50c83f050bd1423c764d60a99)
- Enhance: デッキのアンテナ・リスト選択画面からそれぞれを新規作成できるように
Expand Down Expand Up @@ -57,6 +61,8 @@
- Fix: ダイレクト投稿の"削除して編集"において、宛先が保持されていなかった問題を修正
- Fix: 投稿フォームへのURL貼り付けによる引用が下書きに保存されていなかった問題を修正
- Fix: "削除して編集"や下書きにおいて、リアクションの受け入れ設定が保持/保存されていなかった問題を修正
- Fix: 照会に `#` から始まる文字列を入力してそのハッシュタグのページを表示する際、入力が `#` のみの場合に「指定されたURLに該当するページはありませんでした。」が表示されてしまう問題を修正
- Fix: 照会に `@` から始まる文字列を入力してユーザーを照会する際、入力が `@` のみの場合に「問題が発生しました」が表示されてしまう問題を修正
- Fix: 投稿フォームにノートのURLを貼り付けて"引用として添付"した場合、投稿文を空にすることによるRenote化が出来なかった問題を修正

### Server
Expand Down Expand Up @@ -96,6 +102,8 @@
- Fix: リノートのミュートが適用されるまでに時間がかかることがある問題を修正
(Cherry-picked from https://github.com/Type4ny-Project/Type4ny/commit/e9601029b52e0ad43d9131b555b614e56c84ebc1)
- Fix: Steaming APIが不正なデータを受けた場合の動作が不安定である問題 #14251
- Fix: `users/search`において `@` から始まる文字列が与えられた際の処理が正しくなかった問題を修正
- 名前や自己紹介に `@` から始まる文言が含まれるユーザーも検索できるようになります
- Fix: 一部のMisskey以外のソフトウェアからファイルを受け取れない問題
(Cherry-picked from https://github.com/Secineralyr/misskey.dream/pull/73/commits/652eaff1e8aa00b890d71d2e1e52c263c1e67c76)
- NOTE: `drive_file`の`url`, `uri`, `src`の上限が512から1024に変更されます
Expand Down
8 changes: 8 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4500,6 +4500,14 @@ export interface Locale extends ILocale {
* ユーザー指定
*/
"specifyUser": string;
/**
* 照会しますか?
*/
"lookupConfirm": string;
/**
* ハッシュタグのページを開きますか?
*/
"openTagPageConfirm": string;
/**
* ホスト指定
*/
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,8 @@ preventAiLearning: "生成AIによる学習を拒否"
preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。"
options: "オプション"
specifyUser: "ユーザー指定"
lookupConfirm: "照会しますか?"
openTagPageConfirm: "ハッシュタグのページを開きますか?"
specifyHost: "ホスト指定"
failedToPreviewUrl: "プレビューできません"
update: "更新"
Expand Down
108 changes: 43 additions & 65 deletions packages/backend/src/server/api/endpoints/users/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,88 +57,66 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日

ps.query = ps.query.trim();
const isUsername = ps.query.startsWith('@');
const isUsername = ps.query.startsWith('@') && !ps.query.includes(' ') && ps.query.indexOf('@', 1) === -1;

let users: MiUser[] = [];

if (isUsername) {
const usernameQuery = this.usersRepository.createQueryBuilder('user')
.where('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' })
.andWhere(new Brackets(qb => {
qb
.where('user.updatedAt IS NULL')
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
}))
.andWhere('user.isSuspended = FALSE');
const nameQuery = this.usersRepository.createQueryBuilder('user')
.where(new Brackets(qb => {
qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });

if (isUsername) {
qb.orWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' });
} else if (this.userEntityService.validateLocalUsername(ps.query)) { // Also search username if it qualifies as username
qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' });
}
}))
.andWhere(new Brackets(qb => {
qb
.where('user.updatedAt IS NULL')
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
}))
.andWhere('user.isSuspended = FALSE');

if (ps.origin === 'local') {
nameQuery.andWhere('user.host IS NULL');
} else if (ps.origin === 'remote') {
nameQuery.andWhere('user.host IS NOT NULL');
}

users = await nameQuery
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.offset(ps.offset)
.getMany();

if (users.length < ps.limit) {
const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
.select('prof.userId')
.where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });

if (ps.origin === 'local') {
usernameQuery.andWhere('user.host IS NULL');
profQuery.andWhere('prof.userHost IS NULL');
} else if (ps.origin === 'remote') {
usernameQuery.andWhere('user.host IS NOT NULL');
profQuery.andWhere('prof.userHost IS NOT NULL');
}

users = await usernameQuery
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.offset(ps.offset)
.getMany();
} else {
const nameQuery = this.usersRepository.createQueryBuilder('user')
.where(new Brackets(qb => {
qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });

// Also search username if it qualifies as username
if (this.userEntityService.validateLocalUsername(ps.query)) {
qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' });
}
}))
const query = this.usersRepository.createQueryBuilder('user')
.where(`user.id IN (${ profQuery.getQuery() })`)
.andWhere(new Brackets(qb => {
qb
.where('user.updatedAt IS NULL')
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
}))
.andWhere('user.isSuspended = FALSE');

if (ps.origin === 'local') {
nameQuery.andWhere('user.host IS NULL');
} else if (ps.origin === 'remote') {
nameQuery.andWhere('user.host IS NOT NULL');
}
.andWhere('user.isSuspended = FALSE')
.setParameters(profQuery.getParameters());

users = await nameQuery
users = users.concat(await query
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.offset(ps.offset)
.getMany();

if (users.length < ps.limit) {
const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
.select('prof.userId')
.where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });

if (ps.origin === 'local') {
profQuery.andWhere('prof.userHost IS NULL');
} else if (ps.origin === 'remote') {
profQuery.andWhere('prof.userHost IS NOT NULL');
}

const query = this.usersRepository.createQueryBuilder('user')
.where(`user.id IN (${ profQuery.getQuery() })`)
.andWhere(new Brackets(qb => {
qb
.where('user.updatedAt IS NULL')
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
}))
.andWhere('user.isSuspended = FALSE')
.setParameters(profQuery.getParameters());

users = users.concat(await query
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.offset(ps.offset)
.getMany(),
);
}
.getMany(),
);
}

return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/components/MkInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const props = defineProps<{
const emit = defineEmits<{
(ev: 'change', _ev: KeyboardEvent): void;
(ev: 'keydown', _ev: KeyboardEvent): void;
(ev: 'enter'): void;
(ev: 'enter', _ev: KeyboardEvent): void;
(ev: 'update:modelValue', value: string | number): void;
}>();

Expand Down Expand Up @@ -111,7 +111,7 @@ const onKeydown = (ev: KeyboardEvent) => {
emit('keydown', ev);

if (ev.code === 'Enter') {
emit('enter');
emit('enter', ev);
}
};

Expand Down
54 changes: 42 additions & 12 deletions packages/frontend/src/pages/search.note.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkFoldableSection :expanded="true">
Expand Down Expand Up @@ -143,25 +143,55 @@ async function search() {
if (query == null || query === '') return;

//#region AP lookup
if (query.startsWith('https://')) {
const promise = misskeyApi('ap/show', {
uri: query,
if (query.startsWith('https://') && !query.includes(' ')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
const promise = misskeyApi('ap/show', {
uri: query,
});

os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);

const res = await promise;
const res = await promise;

if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}
if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}

return;
return;
}
}
//#endregion

if (query.length > 1 && !query.includes(' ')) {
if (query.startsWith('@')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
router.push(`/${query}`);
return;
}
}

if (query.startsWith('#')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.openTagPageConfirm,
});
if (!confirm.canceled) {
router.push(`/tags/${encodeURIComponent(query.substring(1))}`);
return;
}
}
}

notePagination.value = {
endpoint: 'notes/search',
limit: 10,
Expand Down
58 changes: 44 additions & 14 deletions packages/frontend/src/pages/search.user.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter.prevent="search">
<template #prefix><i class="ti ti-search"></i></template>
</MkInput>
<MkRadios v-model="searchOrigin" @update:modelValue="search()">
Expand Down Expand Up @@ -39,8 +39,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { useRouter } from '@/router/supplier.js';

const props = withDefaults(defineProps<{
query?: string,
origin?: Endpoints['users/search']['req']['origin'],
query?: string,
origin?: Endpoints['users/search']['req']['origin'],
}>(), {
query: '',
origin: 'combined',
Expand All @@ -59,25 +59,55 @@ async function search() {
if (query == null || query === '') return;

//#region AP lookup
if (query.startsWith('https://')) {
const promise = misskeyApi('ap/show', {
uri: query,
if (query.startsWith('https://') && !query.includes(' ')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
const promise = misskeyApi('ap/show', {
uri: query,
});

os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);

const res = await promise;
const res = await promise;

if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}
if (res.type === 'User') {
router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type === 'Note') {
router.push(`/notes/${res.object.id}`);
}

return;
return;
}
}
//#endregion

if (query.length > 1 && !query.includes(' ')) {
if (query.startsWith('@')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.lookupConfirm,
});
if (!confirm.canceled) {
router.push(`/${query}`);
return;
}
}

if (query.startsWith('#')) {
const confirm = await os.confirm({
type: 'info',
text: i18n.ts.openTagPageConfirm,
});
if (!confirm.canceled) {
router.push(`/user-tags/${encodeURIComponent(query.substring(1))}`);
return;
}
}
}

userPagination.value = {
endpoint: 'users/search',
limit: 10,
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/scripts/lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function lookup(router?: Router) {
title: i18n.ts.lookup,
});
const query = temp ? temp.trim() : '';
if (canceled) return;
if (canceled || query.length <= 1) return;

if (query.startsWith('@') && !query.includes(' ')) {
_router.push(`/${query}`);
Expand Down
Loading