fix inconsistent following feed filters on mobile
This commit is contained in:
parent
1520bc1715
commit
64e4cf8277
|
@ -39,13 +39,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export const followingTab = 'following' as const;
|
|
||||||
export const mutualsTab = 'mutuals' as const;
|
|
||||||
export const followersTab = 'followers' as const;
|
|
||||||
export type FollowingFeedTab = typeof followingTab | typeof mutualsTab | typeof followersTab;
|
|
||||||
</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';
|
||||||
|
@ -60,54 +53,24 @@ import { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
import { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
|
import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import MkPageHeader from '@/components/global/MkPageHeader.vue';
|
import MkPageHeader from '@/components/global/MkPageHeader.vue';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||||
import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
|
import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
|
||||||
import { useScrollPositionManager } from '@/nirax.js';
|
import { useScrollPositionManager } from '@/nirax.js';
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
import { deepMerge } from '@/scripts/merge.js';
|
|
||||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
import { createModel, createOptions, followersTab, followingTab, mutualsTab } from '@/scripts/following-feed-utils.js';
|
||||||
|
|
||||||
const withNonPublic = computed({
|
const {
|
||||||
get: () => {
|
userList,
|
||||||
if (userList.value === 'followers') return false;
|
withNonPublic,
|
||||||
return defaultStore.reactiveState.followingFeed.value.withNonPublic;
|
withQuotes,
|
||||||
},
|
withBots,
|
||||||
set: value => saveFollowingFilter('withNonPublic', value),
|
withReplies,
|
||||||
});
|
onlyFiles,
|
||||||
const withQuotes = computed({
|
remoteWarningDismissed,
|
||||||
get: () => defaultStore.reactiveState.followingFeed.value.withQuotes,
|
} = createModel();
|
||||||
set: value => saveFollowingFilter('withQuotes', value),
|
|
||||||
});
|
|
||||||
const withBots = computed({
|
|
||||||
get: () => defaultStore.reactiveState.followingFeed.value.withBots,
|
|
||||||
set: value => saveFollowingFilter('withBots', value),
|
|
||||||
});
|
|
||||||
const withReplies = computed({
|
|
||||||
get: () => defaultStore.reactiveState.followingFeed.value.withReplies,
|
|
||||||
set: value => saveFollowingFilter('withReplies', value),
|
|
||||||
});
|
|
||||||
const onlyFiles = computed({
|
|
||||||
get: () => defaultStore.reactiveState.followingFeed.value.onlyFiles,
|
|
||||||
set: value => saveFollowingFilter('onlyFiles', value),
|
|
||||||
});
|
|
||||||
const userList = computed({
|
|
||||||
get: () => defaultStore.reactiveState.followingFeed.value.userList,
|
|
||||||
set: value => saveFollowingFilter('userList', value),
|
|
||||||
});
|
|
||||||
const remoteWarningDismissed = computed({
|
|
||||||
get: () => defaultStore.reactiveState.followingFeed.value.remoteWarningDismissed,
|
|
||||||
set: value => saveFollowingFilter('remoteWarningDismissed', value),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Based on timeline.saveTlFilter()
|
|
||||||
function saveFollowingFilter<Key extends keyof typeof defaultStore.state.followingFeed>(key: Key, value: (typeof defaultStore.state.followingFeed)[Key]) {
|
|
||||||
const out = deepMerge({ [key]: value }, defaultStore.state.followingFeed);
|
|
||||||
defaultStore.set('followingFeed', out);
|
|
||||||
}
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -215,45 +178,7 @@ const headerActions: PageHeaderItem[] = [
|
||||||
text: i18n.ts.reload,
|
text: i18n.ts.reload,
|
||||||
handler: () => reload(),
|
handler: () => reload(),
|
||||||
},
|
},
|
||||||
{
|
createOptions(),
|
||||||
icon: 'ti ti-dots',
|
|
||||||
text: i18n.ts.options,
|
|
||||||
handler: (ev) => {
|
|
||||||
os.popupMenu([
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.showNonPublicNotes,
|
|
||||||
ref: withNonPublic,
|
|
||||||
disabled: userList.value === 'followers',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.showQuotes,
|
|
||||||
ref: withQuotes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.showBots,
|
|
||||||
ref: withBots,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.showReplies,
|
|
||||||
ref: withReplies,
|
|
||||||
disabled: onlyFiles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.fileAttachedOnly,
|
|
||||||
ref: onlyFiles,
|
|
||||||
disabled: withReplies,
|
|
||||||
},
|
|
||||||
], ev.currentTarget ?? ev.target);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const headerTabs = computed(() => [
|
const headerTabs = computed(() => [
|
||||||
|
|
|
@ -4,24 +4,25 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer ref="userScroll">
|
||||||
<template #header>
|
<template #header>
|
||||||
<MkPageHeader :actions="headerActions" :displayBackButton="true"/>
|
<MkPageHeader :actions="headerActions" :displayBackButton="true"/>
|
||||||
</template>
|
</template>
|
||||||
<SkUserRecentNotes ref="userRecentNotes" :userId="userId" :withRenotes="withRenotes" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
|
<SkUserRecentNotes ref="userRecentNotes" :userId="userId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withBots="withBots" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import { computed, ref, shallowRef } from 'vue';
|
import { computed, shallowRef } from 'vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { PageHeaderItem } from '@/types/page-header.js';
|
import { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import MkPageHeader from '@/components/global/MkPageHeader.vue';
|
import MkPageHeader from '@/components/global/MkPageHeader.vue';
|
||||||
import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
|
import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
|
||||||
import { acct } from '@/filters/user.js';
|
import { acct } from '@/filters/user.js';
|
||||||
|
import { createModel, createOptions } from '@/scripts/following-feed-utils.js';
|
||||||
|
import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -29,43 +30,22 @@ defineProps<{
|
||||||
|
|
||||||
const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>();
|
const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>();
|
||||||
const user = computed(() => userRecentNotes.value?.user);
|
const user = computed(() => userRecentNotes.value?.user);
|
||||||
const withRenotes = ref(false);
|
|
||||||
const withReplies = ref(true);
|
|
||||||
const onlyFiles = ref(false);
|
|
||||||
|
|
||||||
const headerActions = [
|
const {
|
||||||
|
withNonPublic,
|
||||||
|
withQuotes,
|
||||||
|
withBots,
|
||||||
|
withReplies,
|
||||||
|
onlyFiles,
|
||||||
|
} = createModel();
|
||||||
|
|
||||||
|
const headerActions: PageHeaderItem[] = [
|
||||||
{
|
{
|
||||||
icon: 'ti ti-refresh',
|
icon: 'ti ti-refresh',
|
||||||
text: i18n.ts.reload,
|
text: i18n.ts.reload,
|
||||||
handler: () => userRecentNotes.value?.reload(),
|
handler: () => userRecentNotes.value?.reload(),
|
||||||
} satisfies PageHeaderItem,
|
|
||||||
{
|
|
||||||
icon: 'ti ti-dots',
|
|
||||||
text: i18n.ts.options,
|
|
||||||
handler: (ev) => {
|
|
||||||
os.popupMenu([
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.showRenotes,
|
|
||||||
ref: withRenotes,
|
|
||||||
}, {
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.showRepliesToOthersInTimeline,
|
|
||||||
ref: withReplies,
|
|
||||||
disabled: onlyFiles,
|
|
||||||
},
|
},
|
||||||
{
|
createOptions(),
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'switch',
|
|
||||||
text: i18n.ts.fileAttachedOnly,
|
|
||||||
ref: onlyFiles,
|
|
||||||
disabled: withReplies,
|
|
||||||
},
|
|
||||||
], ev.currentTarget ?? ev.target);
|
|
||||||
},
|
|
||||||
} satisfies PageHeaderItem,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Based on user/index.vue
|
// Based on user/index.vue
|
||||||
|
@ -83,9 +63,4 @@ definePageMetadata(() => ({
|
||||||
},
|
},
|
||||||
} : {},
|
} : {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
|
import { deepMerge } from '@/scripts/merge.js';
|
||||||
|
import { PageHeaderItem } from '@/types/page-header.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { popupMenu } from '@/os.js';
|
||||||
|
|
||||||
|
export const followingTab = 'following' as const;
|
||||||
|
export const mutualsTab = 'mutuals' as const;
|
||||||
|
export const followersTab = 'followers' as const;
|
||||||
|
export type FollowingFeedTab = typeof followingTab | typeof mutualsTab | typeof followersTab;
|
||||||
|
|
||||||
|
export function createOptions(): PageHeaderItem {
|
||||||
|
const {
|
||||||
|
userList,
|
||||||
|
withNonPublic,
|
||||||
|
withQuotes,
|
||||||
|
withBots,
|
||||||
|
withReplies,
|
||||||
|
onlyFiles,
|
||||||
|
} = createModel();
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon: 'ti ti-dots',
|
||||||
|
text: i18n.ts.options,
|
||||||
|
handler: ev =>
|
||||||
|
popupMenu([
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showNonPublicNotes,
|
||||||
|
ref: withNonPublic,
|
||||||
|
disabled: userList.value === 'followers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showQuotes,
|
||||||
|
ref: withQuotes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showBots,
|
||||||
|
ref: withBots,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showReplies,
|
||||||
|
ref: withReplies,
|
||||||
|
disabled: onlyFiles,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.fileAttachedOnly,
|
||||||
|
ref: onlyFiles,
|
||||||
|
disabled: withReplies,
|
||||||
|
},
|
||||||
|
], ev.currentTarget ?? ev.target),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createModel() {
|
||||||
|
const userList = computed({
|
||||||
|
get: () => defaultStore.reactiveState.followingFeed.value.userList,
|
||||||
|
set: value => saveFollowingFilter('userList', value),
|
||||||
|
});
|
||||||
|
|
||||||
|
const withNonPublic = computed({
|
||||||
|
get: () => {
|
||||||
|
if (userList.value === 'followers') return false;
|
||||||
|
return defaultStore.reactiveState.followingFeed.value.withNonPublic;
|
||||||
|
},
|
||||||
|
set: value => saveFollowingFilter('withNonPublic', value),
|
||||||
|
});
|
||||||
|
const withQuotes = computed({
|
||||||
|
get: () => defaultStore.reactiveState.followingFeed.value.withQuotes,
|
||||||
|
set: value => saveFollowingFilter('withQuotes', value),
|
||||||
|
});
|
||||||
|
const withBots = computed({
|
||||||
|
get: () => defaultStore.reactiveState.followingFeed.value.withBots,
|
||||||
|
set: value => saveFollowingFilter('withBots', value),
|
||||||
|
});
|
||||||
|
const withReplies = computed({
|
||||||
|
get: () => defaultStore.reactiveState.followingFeed.value.withReplies,
|
||||||
|
set: value => saveFollowingFilter('withReplies', value),
|
||||||
|
});
|
||||||
|
const onlyFiles = computed({
|
||||||
|
get: () => defaultStore.reactiveState.followingFeed.value.onlyFiles,
|
||||||
|
set: value => saveFollowingFilter('onlyFiles', value),
|
||||||
|
});
|
||||||
|
|
||||||
|
const remoteWarningDismissed = computed({
|
||||||
|
get: () => defaultStore.reactiveState.followingFeed.value.remoteWarningDismissed,
|
||||||
|
set: value => saveFollowingFilter('remoteWarningDismissed', value),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
userList,
|
||||||
|
withNonPublic,
|
||||||
|
withQuotes,
|
||||||
|
withBots,
|
||||||
|
withReplies,
|
||||||
|
onlyFiles,
|
||||||
|
remoteWarningDismissed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on timeline.saveTlFilter()
|
||||||
|
function saveFollowingFilter<Key extends keyof typeof defaultStore.state.followingFeed>(key: Key, value: (typeof defaultStore.state.followingFeed)[Key]) {
|
||||||
|
const out = deepMerge({ [key]: value }, defaultStore.state.followingFeed);
|
||||||
|
return defaultStore.set('followingFeed', out);
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import darkTheme from '@@/themes/d-ice.json5';
|
||||||
import { miLocalStorage } from './local-storage.js';
|
import { miLocalStorage } from './local-storage.js';
|
||||||
import { searchEngineMap } from './scripts/search-engine-map.js';
|
import { searchEngineMap } from './scripts/search-engine-map.js';
|
||||||
import type { SoundType } from '@/scripts/sound.js';
|
import type { SoundType } from '@/scripts/sound.js';
|
||||||
import type { FollowingFeedTab } from '@/pages/following-feed.vue';
|
import type { FollowingFeedTab } from '@/scripts/following-feed-utils.js';
|
||||||
import { Storage } from '@/pizzax.js';
|
import { Storage } from '@/pizzax.js';
|
||||||
|
|
||||||
interface PostFormAction {
|
interface PostFormAction {
|
||||||
|
|
Loading…
Reference in New Issue