diff --git a/locales/en-US.yml b/locales/en-US.yml
index a8dd71c41..eac6d0c49 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1178,6 +1178,7 @@ useCdn: "Get assets from CDN"
useCdnDescription: "Load some static assets like Twemoji from the JSDelivr CDN instead of this Firefish server."
suggested: "Suggested"
noLanguage: "No language"
+searchCwAndAlt: "Include content warnings and alt texts"
_sensitiveMediaDetection:
description: "Reduces the effort of server moderation through automatically recognizing
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 971a8f10b..467d902b0 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -2057,3 +2057,4 @@ searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@e
searchRange: "投稿期間(オプション)"
searchRangeDescription: "投稿検索で投稿期間を絞りたい場合、20220615-20231031 のような形式で投稿期間を入力してください。今年の日付を指定する場合には年の指定を省略できます(0105-0106 や 20231105-0110 のように)。\n\n開始日と終了日のどちらか一方は省略可能です。例えば -0102 とすると今年1月2日までの投稿のみを、20231026- とすると2023年10月26日以降の投稿のみを検索します。"
searchPostsWithFiles: "添付ファイルのある投稿のみ"
+searchCwAndAlt: "閲覧注意の注釈と添付ファイルの代替テキストも検索する"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 3ce09488e..584e67459 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -2050,3 +2050,4 @@ searchUsersDescription: "如欲搜尋特定使用者的貼文,請以「@user@e
searchWords: "搜尋關鍵字 / 查詢ID或URL"
searchWordsDescription: "如欲搜尋貼文,請在此欄位輸入欲搜尋的關鍵字。以空格分隔關鍵字以進行AND搜尋、在關鍵字之間插入「OR」以進行OR搜尋。\n舉例來說,輸入「早上 晚上」會搜尋包含「早上」和「晚上」的貼文,「早上 OR 晚上」會搜尋包含「早上」或「晚上」(或兩者皆包含)的貼文。\n\n如欲前往特定使用者或貼文的頁面,請在此欄位輸入使用者ID(@user@example.com)或貼文的URL,並點擊「查詢」按鈕。點擊「搜尋」按鈕則會搜尋字面上包含輸入的ID或URL的貼文。"
suggested: "建議"
+searchCwAndAlt: "包含內容警告及替代文字"
diff --git a/neko/revert.sql b/neko/revert.sql
index f9ce2c7a8..729992b95 100644
--- a/neko/revert.sql
+++ b/neko/revert.sql
@@ -1,6 +1,7 @@
BEGIN;
DELETE FROM "migrations" WHERE name IN (
+ 'IndexAltTextAndCw1708872574733',
'SeparateHardMuteWordsAndPatterns1706413792769',
'RenameMetaColumns1705944717480',
'RemoveNativeUtilsMigration1705877093218',
@@ -13,6 +14,10 @@ DELETE FROM "migrations" WHERE name IN (
'EmojiModerator1692825433698'
);
+-- index-alt-text-and-cw
+DROP INDEX "IDX_f4f7b93d05958527300d79ac82";
+DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f";
+
-- separate-hard-mute-words-and-patterns
UPDATE "user_profile" SET "mutedWords" = "mutedWords" || array_to_json("mutedPatterns")::jsonb;
ALTER TABLE "user_profile" DROP "mutedPatterns";
diff --git a/packages/backend/migration-neko/1708872574733-index-alt-text-and-cw.js b/packages/backend/migration-neko/1708872574733-index-alt-text-and-cw.js
new file mode 100644
index 000000000..dbe4ee3c1
--- /dev/null
+++ b/packages/backend/migration-neko/1708872574733-index-alt-text-and-cw.js
@@ -0,0 +1,17 @@
+export class IndexAltTextAndCw1708872574733 {
+ name = "IndexAltTextAndCw1708872574733";
+
+ async up(queryRunner) {
+ await queryRunner.query(
+ `CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2)`,
+ );
+ await queryRunner.query(
+ `CREATE INDEX "IDX_f4f7b93d05958527300d79ac82" ON "drive_file" USING "pgroonga" ("comment" pgroonga_varchar_full_text_search_ops_v2)`,
+ );
+ }
+
+ async down(queryRunner) {
+ await queryRunner.query(`DROP INDEX "IDX_f4f7b93d05958527300d79ac82"`);
+ await queryRunner.query(`DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"`);
+ }
+}
diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts
index 52ab027be..db45b536b 100644
--- a/packages/backend/src/models/entities/drive-file.ts
+++ b/packages/backend/src/models/entities/drive-file.ts
@@ -70,6 +70,7 @@ export class DriveFile {
})
public size: number;
+ @Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: DB_MAX_IMAGE_COMMENT_LENGTH,
nullable: true,
diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts
index 79d9ade78..a61f167c1 100644
--- a/packages/backend/src/models/entities/note.ts
+++ b/packages/backend/src/models/entities/note.ts
@@ -79,6 +79,7 @@ export class Note {
})
public name: string | null;
+ @Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column("varchar", {
length: 512,
nullable: true,
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index bc17944bb..cc97e6f62 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -1,4 +1,5 @@
// import { FindManyOptions, In } from "typeorm";
+import { Brackets } from "typeorm";
import { Notes } from "@/models/index.js";
import { Note } from "@/models/entities/note.js";
// import config from "@/config/index.js";
@@ -56,6 +57,7 @@ export const paramDef = {
default: null,
},
withFiles: { type: "boolean", nullable: true },
+ searchCwAndAlt: { type: "boolean", nullable: true },
channelId: {
type: "string",
format: "misskey:id",
@@ -92,7 +94,28 @@ export default define(meta, paramDef, async (ps, me) => {
}
if (ps.query != null) {
- query.andWhere("note.text &@~ :q", { q: `${sqlLikeEscape(ps.query)}` });
+ const q = sqlLikeEscape(ps.query);
+
+ if (ps.searchCwAndAlt) {
+ query.andWhere(
+ new Brackets((qb) => {
+ qb.where("note.text &@~ :q", { q })
+ .orWhere("note.cw &@~ :q", { q })
+ .orWhere(
+ `EXISTS (
+ SELECT FROM "drive_file"
+ WHERE
+ comment &@~ :q
+ AND
+ drive_file."id" = ANY(note."fileIds")
+ )`,
+ { q },
+ );
+ }),
+ );
+ } else {
+ query.andWhere("note.text &@~ :q", { q });
+ }
}
query.innerJoinAndSelect("note.user", "user");
diff --git a/packages/client/src/components/MkSearchBox.vue b/packages/client/src/components/MkSearchBox.vue
index bb61a982d..e702fd76a 100644
--- a/packages/client/src/components/MkSearchBox.vue
+++ b/packages/client/src/components/MkSearchBox.vue
@@ -68,6 +68,12 @@
:class="$style.input"
>{{ i18n.ts.searchPostsWithFiles }}
+