add "show bots" toggle to following feed

This commit is contained in:
Hazelnoot 2024-10-12 14:56:04 -04:00
parent 24fd35e03d
commit 9b1bae653d
6 changed files with 35 additions and 7 deletions

View file

@ -37,6 +37,7 @@ export const paramDef = {
includeNonPublic: { type: 'boolean', default: false }, includeNonPublic: { type: 'boolean', default: false },
includeReplies: { type: 'boolean', default: false }, includeReplies: { type: 'boolean', default: false },
includeQuotes: { type: 'boolean', default: false }, includeQuotes: { type: 'boolean', default: false },
includeBots: { type: 'boolean', default: true },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' }, sinceId: { type: 'string', format: 'misskey:id' },
@ -97,6 +98,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
query.andWhere('latest.is_quote = false'); query.andWhere('latest.is_quote = false');
} }
// Match selected user types.
if (!ps.includeBots) {
query.andWhere('"user"."isBot" = false');
}
// Respect blocks and mutes // Respect blocks and mutes
this.queryService.generateBlockedUserQuery(query, me); this.queryService.generateBlockedUserQuery(query, me);
this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedUserQuery(query, me);

View file

@ -55,6 +55,7 @@ export const paramDef = {
withRepliesToSelf: { type: 'boolean', default: true }, withRepliesToSelf: { type: 'boolean', default: true },
withQuotes: { type: 'boolean', default: true }, withQuotes: { type: 'boolean', default: true },
withRenotes: { type: 'boolean', default: true }, withRenotes: { type: 'boolean', default: true },
withBots: { type: 'boolean', default: true },
withNonPublic: { type: 'boolean', default: true }, withNonPublic: { type: 'boolean', default: true },
withChannelNotes: { type: 'boolean', default: false }, withChannelNotes: { type: 'boolean', default: false },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
@ -108,6 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withFiles: ps.withFiles, withFiles: ps.withFiles,
withRenotes: ps.withRenotes, withRenotes: ps.withRenotes,
withQuotes: ps.withQuotes, withQuotes: ps.withQuotes,
withBots: ps.withBots,
withNonPublic: ps.withNonPublic, withNonPublic: ps.withNonPublic,
withRepliesToOthers: ps.withReplies, withRepliesToOthers: ps.withReplies,
withRepliesToSelf: ps.withRepliesToSelf, withRepliesToSelf: ps.withRepliesToSelf,
@ -135,6 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies
excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files
excludePureRenotes: !ps.withRenotes, excludePureRenotes: !ps.withRenotes,
excludeBots: !ps.withBots,
noteFilter: note => { noteFilter: note => {
if (note.channel?.isSensitive && !isSelf) return false; if (note.channel?.isSensitive && !isSelf) return false;
if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false; if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
@ -156,6 +159,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withFiles: ps.withFiles, withFiles: ps.withFiles,
withRenotes: ps.withRenotes, withRenotes: ps.withRenotes,
withQuotes: ps.withQuotes, withQuotes: ps.withQuotes,
withBots: ps.withBots,
withNonPublic: ps.withNonPublic, withNonPublic: ps.withNonPublic,
withRepliesToOthers: ps.withReplies, withRepliesToOthers: ps.withReplies,
withRepliesToSelf: ps.withRepliesToSelf, withRepliesToSelf: ps.withRepliesToSelf,
@ -175,6 +179,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
withFiles: boolean, withFiles: boolean,
withRenotes: boolean, withRenotes: boolean,
withQuotes: boolean, withQuotes: boolean,
withBots: boolean,
withNonPublic: boolean, withNonPublic: boolean,
withRepliesToOthers: boolean, withRepliesToOthers: boolean,
withRepliesToSelf: boolean, withRepliesToSelf: boolean,
@ -246,6 +251,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
query.andWhere('note.visibility = \'public\''); query.andWhere('note.visibility = \'public\'');
} }
if (!ps.withBots) {
query.andWhere('"user"."isBot" = false');
}
return await query.limit(ps.limit).getMany(); return await query.limit(ps.limit).getMany();
} }
} }

View file

@ -29,6 +29,7 @@ const props = defineProps<{
withNonPublic: boolean; withNonPublic: boolean;
withQuotes: boolean; withQuotes: boolean;
withReplies: boolean; withReplies: boolean;
withBots: boolean;
onlyFiles: boolean; onlyFiles: boolean;
}>(); }>();

View file

@ -30,18 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="isWideViewport" ref="userScroll" :class="$style.user"> <div v-if="isWideViewport" ref="userScroll" :class="$style.user">
<MkHorizontalSwipe v-if="selectedUserId" v-model:tab="currentTab" :tabs="headerTabs"> <MkHorizontalSwipe v-if="selectedUserId" v-model:tab="currentTab" :tabs="headerTabs">
<SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withReplies="withReplies" :onlyFiles="onlyFiles"/> <SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withBots="withBots" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
</MkHorizontalSwipe> </MkHorizontalSwipe>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts">
export type FollowingFeedTab = typeof followingTab | typeof mutualsTab;
export const followingTab = 'following' as const;
export const mutualsTab = 'mutuals' as const;
</script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, Ref, ref, shallowRef } from 'vue'; import { computed, Ref, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
@ -74,6 +68,10 @@ const withQuotes = computed({
get: () => defaultStore.reactiveState.followingFeed.value.withQuotes, get: () => defaultStore.reactiveState.followingFeed.value.withQuotes,
set: value => saveFollowingFilter('withQuotes', value), set: value => saveFollowingFilter('withQuotes', value),
}); });
const withBots = computed({
get: () => defaultStore.reactiveState.followingFeed.value.withBots,
set: value => saveFollowingFilter('withBots', value),
});
const withReplies = computed({ const withReplies = computed({
get: () => defaultStore.reactiveState.followingFeed.value.withReplies, get: () => defaultStore.reactiveState.followingFeed.value.withReplies,
set: value => saveFollowingFilter('withReplies', value), set: value => saveFollowingFilter('withReplies', value),
@ -95,10 +93,13 @@ function saveFollowingFilter(key: keyof typeof defaultStore.state.followingFeed,
const router = useRouter(); const router = useRouter();
const followingTab = 'following' as const;
const mutualsTab = 'mutuals' as const;
const currentTab = computed({ const currentTab = computed({
get: () => onlyMutuals.value ? mutualsTab : followingTab, get: () => onlyMutuals.value ? mutualsTab : followingTab,
set: value => onlyMutuals.value = (value === mutualsTab), set: value => onlyMutuals.value = (value === mutualsTab),
}); });
const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>(); const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>();
const userScroll = shallowRef<HTMLElement>(); const userScroll = shallowRef<HTMLElement>();
const noteScroll = shallowRef<HTMLElement>(); const noteScroll = shallowRef<HTMLElement>();
@ -188,6 +189,7 @@ const latestNotesPagination: Paging<'notes/following'> = {
includeNonPublic: withNonPublic.value, includeNonPublic: withNonPublic.value,
includeReplies: withReplies.value, includeReplies: withReplies.value,
includeQuotes: withQuotes.value, includeQuotes: withQuotes.value,
includeBots: withBots.value,
})), })),
}; };
@ -212,6 +214,11 @@ const headerActions: PageHeaderItem[] = [
text: i18n.ts.showQuotes, text: i18n.ts.showQuotes,
ref: withQuotes, ref: withQuotes,
}, },
{
type: 'switch',
text: i18n.ts.showBots,
ref: withBots,
},
{ {
type: 'switch', type: 'switch',
text: i18n.ts.showReplies, text: i18n.ts.showReplies,

View file

@ -244,6 +244,7 @@ export const defaultStore = markRaw(new Storage('base', {
default: { default: {
withNonPublic: false, withNonPublic: false,
withQuotes: false, withQuotes: false,
withBots: true,
withReplies: false, withReplies: false,
onlyFiles: false, onlyFiles: false,
onlyMutuals: false, onlyMutuals: false,

View file

@ -22304,6 +22304,8 @@ export type operations = {
includeReplies?: boolean; includeReplies?: boolean;
/** @default false */ /** @default false */
includeQuotes?: boolean; includeQuotes?: boolean;
/** @default true */
includeBots?: boolean;
/** @default 10 */ /** @default 10 */
limit?: number; limit?: number;
/** Format: misskey:id */ /** Format: misskey:id */
@ -27242,6 +27244,8 @@ export type operations = {
/** @default true */ /** @default true */
withRenotes?: boolean; withRenotes?: boolean;
/** @default true */ /** @default true */
withBots?: boolean;
/** @default true */
withNonPublic?: boolean; withNonPublic?: boolean;
/** @default false */ /** @default false */
withChannelNotes?: boolean; withChannelNotes?: boolean;