diff --git a/locales/en-US.yml b/locales/en-US.yml index 863b180514d1..576193522f57 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -391,6 +391,7 @@ enableServiceworker: "Enable Push-Notifications for your Browser" antennaUsersDescription: "List one username per line" caseSensitive: "Case sensitive" withReplies: "Include replies" +withHashtags: "Include notes with hashtags" connectedTo: "Following account(s) are connected" notesAndReplies: "Notes and replies" withFiles: "Including files" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 008e8518399c..70d14efeb73e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -391,6 +391,7 @@ enableServiceworker: "ブラウザへのプッシュ通知を有効にする" antennaUsersDescription: "ユーザー名を改行で区切って指定します" caseSensitive: "大文字小文字を区別する" withReplies: "返信を含む" +withHashtags: "ハッシュタグ付きを含む" connectedTo: "次のアカウントに接続されています" notesAndReplies: "投稿と返信" withFiles: "ファイル付き" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index bf945088f0ff..dcf8b2ed30b9 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -390,6 +390,7 @@ enableServiceworker: "ブラウザにプッシュ通知が行くようにする" antennaUsersDescription: "ユーザー名を改行で区切ったってな" caseSensitive: "大文字と小文字は別もんや" withReplies: "返信も入れたって" +withHashtags: "ハッシュタグのあるもんも入れたって" connectedTo: "次のアカウントに繋がっとるで" notesAndReplies: "投稿と返信" withFiles: "ファイル付いとる" diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 8784e8615377..7978487b5190 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -42,6 +42,7 @@ export const paramDef = { withFiles: { type: 'boolean', default: false }, withReplies: { type: 'boolean', default: false }, withRenotes: { type: 'boolean', default: true }, + withHashtags: { type: 'boolean', default: true }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, @@ -100,6 +101,10 @@ export default class extends Endpoint { // eslint- })); })); } + + if (!ps.withHashtags) { + query.andWhere('note.tags = \'{}\''); + } //#endregion const timeline = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 9bde5dee21a6..200652638001 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -53,6 +53,7 @@ export const paramDef = { withFiles: { type: 'boolean', default: false }, withReplies: { type: 'boolean', default: false }, withRenotes: { type: 'boolean', default: true }, + withHashtags: { type: 'boolean', default: true }, }, required: [], } as const; @@ -148,6 +149,10 @@ export default class extends Endpoint { // eslint- })); })); } + + if (!ps.withHashtags) { + query.andWhere('note.tags = \'{}\''); + } //#endregion const timeline = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 0fefddc51b02..169c7c4dfe3f 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -43,6 +43,7 @@ export const paramDef = { withFiles: { type: 'boolean', default: false }, withReplies: { type: 'boolean', default: false }, withRenotes: { type: 'boolean', default: true }, + withHashtags: { type: 'boolean', default: true }, fileType: { type: 'array', items: { type: 'string', } }, @@ -121,6 +122,10 @@ export default class extends Endpoint { // eslint- })); })); } + + if (!ps.withHashtags) { + query.andWhere('note.tags = \'{}\''); + } //#endregion const timeline = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 0d47cc17020f..fbf531ee9e93 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -43,6 +43,7 @@ export const paramDef = { withFiles: { type: 'boolean', default: false }, withReplies: { type: 'boolean', default: false }, withRenotes: { type: 'boolean', default: true }, + withHashtags: { type: 'boolean', default: true }, }, required: [], } as const; @@ -137,6 +138,10 @@ export default class extends Endpoint { // eslint- })); })); } + + if (!ps.withHashtags) { + query.andWhere('note.tags = \'{}\''); + } //#endregion const timeline = await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index fef52b68561e..78760bc4a525 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -20,6 +20,7 @@ class GlobalTimelineChannel extends Channel { public static requireCredential = false; private withReplies: boolean; private withRenotes: boolean; + private withHashtags: boolean; constructor( private metaService: MetaService, @@ -40,6 +41,7 @@ class GlobalTimelineChannel extends Channel { this.withReplies = params.withReplies ?? false; this.withRenotes = params.withRenotes ?? true; + this.withHashtags = params.withHashtags ?? true; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -72,6 +74,8 @@ class GlobalTimelineChannel extends Channel { if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + if (note.tags && !this.withHashtags) return; + // Ignore notes from instances the user has muted if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index a75aaf97d092..34b2b46bcbe7 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -18,6 +18,7 @@ class HomeTimelineChannel extends Channel { public static requireCredential = true; private withReplies: boolean; private withRenotes: boolean; + private withHashtags: boolean; constructor( private noteEntityService: NoteEntityService, @@ -33,6 +34,7 @@ class HomeTimelineChannel extends Channel { public async init(params: any) { this.withReplies = params.withReplies ?? false; this.withRenotes = params.withRenotes ?? true; + this.withHashtags = params.withHashtags ?? true; this.subscriber.on('notesStream', this.onNote); } @@ -85,6 +87,8 @@ class HomeTimelineChannel extends Channel { if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + if (note.tags && !this.withHashtags) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 0345f38c7260..8921ae403b8c 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -20,6 +20,7 @@ class HybridTimelineChannel extends Channel { public static requireCredential = true; private withReplies: boolean; private withRenotes: boolean; + private withHashtags: boolean; constructor( private metaService: MetaService, @@ -40,6 +41,7 @@ class HybridTimelineChannel extends Channel { this.withReplies = params.withReplies ?? false; this.withRenotes = params.withRenotes ?? true; + this.withHashtags = params.withHashtags ?? true; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -97,6 +99,8 @@ class HybridTimelineChannel extends Channel { if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + if (note.tags && !this.withHashtags) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index ef708c4fee86..a9745104d91d 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -19,6 +19,7 @@ class LocalTimelineChannel extends Channel { public static requireCredential = false; private withReplies: boolean; private withRenotes: boolean; + private withHashtags: boolean; constructor( private metaService: MetaService, @@ -39,6 +40,7 @@ class LocalTimelineChannel extends Channel { this.withReplies = params.withReplies ?? false; this.withRenotes = params.withRenotes ?? true; + this.withHashtags = params.withHashtags ?? true; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -72,6 +74,8 @@ class LocalTimelineChannel extends Channel { if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + if (note.tags && !this.withHashtags) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 1dcafd6be117..a56f3f750f0d 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -24,10 +24,12 @@ const props = withDefaults(defineProps<{ sound?: boolean; withRenotes?: boolean; withReplies?: boolean; + withHashtags?: boolean; onlyFiles?: boolean; }>(), { withRenotes: true, withReplies: false, + withHashtags: true; onlyFiles: false, }); @@ -71,11 +73,13 @@ if (props.src === 'antenna') { query = { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }; connection = stream.useChannel('homeTimeline', { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }); connection.on('note', prepend); @@ -86,11 +90,13 @@ if (props.src === 'antenna') { query = { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }; connection = stream.useChannel('localTimeline', { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }); connection.on('note', prepend); @@ -99,11 +105,13 @@ if (props.src === 'antenna') { query = { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }; connection = stream.useChannel('hybridTimeline', { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }); connection.on('note', prepend); @@ -112,11 +120,13 @@ if (props.src === 'antenna') { query = { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }; connection = stream.useChannel('globalTimeline', { withRenotes: props.withRenotes, withReplies: props.withReplies, + withHashtags: props.withHashtags, withFiles: props.onlyFiles ? true : undefined, }); connection.on('note', prepend); diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 5bad689aee25..4d397ad115d7 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only :list="src.split(':')[1]" :withRenotes="withRenotes" :withReplies="withReplies" + :withHashtags="withHashtags" :onlyFiles="onlyFiles" :sound="true" @queue="queueUpdated" @@ -63,6 +64,7 @@ let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global'); const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) }); const withRenotes = $ref(true); const withReplies = $ref(false); +const withHashtags = $ref(true); const onlyFiles = $ref(false); watch($$(src), () => queue = 0); @@ -149,6 +151,11 @@ const headerActions = $computed(() => [{ text: i18n.ts.withReplies, icon: 'ti ti-arrow-back-up', ref: $$(withReplies), + }, { + type: 'switch', + text: i18n.ts.withHashtags, + icon: 'ti ti-arrow-back-up', + ref: $$(withHashtags), }, { type: 'switch', text: i18n.ts.fileAttachedOnly,