Merge branch 'develop' into feature/2024.10
This commit is contained in:
commit
6c13dc04f2
26 changed files with 849 additions and 366 deletions
|
|
@ -18,16 +18,21 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
|
||||
@wheel.self="onWheel"
|
||||
>
|
||||
<component
|
||||
:is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn"
|
||||
v-for="id in ids"
|
||||
:ref="id"
|
||||
:key="id"
|
||||
:class="$style.column"
|
||||
:column="columns.find(c => c.id === id)!"
|
||||
:isStacked="ids.length > 1"
|
||||
@headerWheel="onWheel"
|
||||
/>
|
||||
<Suspense>
|
||||
<component
|
||||
:is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn"
|
||||
v-for="id in ids"
|
||||
:ref="id"
|
||||
:key="id"
|
||||
:class="$style.column"
|
||||
:column="columns.find(c => c.id === id)!"
|
||||
:isStacked="ids.length > 1"
|
||||
@headerWheel="onWheel"
|
||||
/>
|
||||
<template #fallback>
|
||||
<MkLoading/>
|
||||
</template>
|
||||
</Suspense>
|
||||
</section>
|
||||
<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
|
||||
<div>{{ i18n.ts._deck.introduction }}</div>
|
||||
|
|
@ -118,6 +123,7 @@ import XWidgetsColumn from '@/ui/deck/widgets-column.vue';
|
|||
import XMentionsColumn from '@/ui/deck/mentions-column.vue';
|
||||
import XDirectColumn from '@/ui/deck/direct-column.vue';
|
||||
import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
|
||||
import XFollowingColumn from '@/ui/deck/following-column.vue';
|
||||
import { mainRouter } from '@/router/main.js';
|
||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
|
||||
|
|
@ -133,6 +139,7 @@ const columnComponents = {
|
|||
mentions: XMentionsColumn,
|
||||
direct: XDirectColumn,
|
||||
roleTimeline: XRoleTimelineColumn,
|
||||
following: XFollowingColumn,
|
||||
};
|
||||
|
||||
mainRouter.navHook = (path, flag): boolean => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { throttle } from 'throttle-debounce';
|
||||
import { markRaw } from 'vue';
|
||||
import { computed, markRaw, Ref } from 'vue';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import type { BasicTimelineType } from '@/timelines.js';
|
||||
import { Storage } from '@/pizzax.js';
|
||||
|
|
@ -29,6 +29,7 @@ export const columnTypes = [
|
|||
'mentions',
|
||||
'direct',
|
||||
'roleTimeline',
|
||||
'following',
|
||||
] as const;
|
||||
|
||||
export type ColumnType = typeof columnTypes[number];
|
||||
|
|
@ -113,8 +114,8 @@ export const loadDeck = async () => {
|
|||
};
|
||||
|
||||
// TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
|
||||
export const saveDeck = throttle(1000, () => {
|
||||
misskeyApi('i/registry/set', {
|
||||
export const saveDeck = throttle(1000, async () => {
|
||||
await misskeyApi('i/registry/set', {
|
||||
scope: ['client', 'deck', 'profiles'],
|
||||
key: deckStore.state.profile,
|
||||
value: {
|
||||
|
|
@ -314,7 +315,7 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat
|
|||
saveDeck();
|
||||
}
|
||||
|
||||
export function updateColumn(id: Column['id'], column: Partial<Column>) {
|
||||
export async function updateColumn<TColumn>(id: Column['id'], column: Partial<TColumn>) {
|
||||
const columns = deepClone(deckStore.state.columns);
|
||||
const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
|
||||
const currentColumn = deepClone(deckStore.state.columns[columnIndex]);
|
||||
|
|
@ -323,6 +324,18 @@ export function updateColumn(id: Column['id'], column: Partial<Column>) {
|
|||
currentColumn[k] = v;
|
||||
}
|
||||
columns[columnIndex] = currentColumn;
|
||||
deckStore.set('columns', columns);
|
||||
saveDeck();
|
||||
await Promise.all([
|
||||
deckStore.set('columns', columns),
|
||||
saveDeck(),
|
||||
]);
|
||||
}
|
||||
|
||||
export function getColumn<TColumn extends Column>(id: Column['id']): TColumn {
|
||||
return deckStore.state.columns.find(c => c.id === id) as TColumn;
|
||||
}
|
||||
|
||||
export function getReactiveColumn<TColumn extends Column>(id: Column['id']): Ref<TColumn> {
|
||||
return computed(() => {
|
||||
return deckStore.reactiveState.columns.value.find(c => c.id === id) as TColumn;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
124
packages/frontend/src/ui/deck/following-column.vue
Normal file
124
packages/frontend/src/ui/deck/following-column.vue
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<!-- based on list-column.vue -->
|
||||
|
||||
<template>
|
||||
<XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="reload">
|
||||
<template #header>
|
||||
<i :class="columnIcon" aria-hidden="true"/><span style="margin-left: 8px;">{{ column.name }}</span>
|
||||
</template>
|
||||
|
||||
<SkRemoteFollowersWarning :class="$style.followersWarning" :model="model"/>
|
||||
<SkFollowingRecentNotes ref="latestNotes" :userList="userList" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withReplies="withReplies" :withBots="withBots" :onlyFiles="onlyFiles" @userSelected="userSelected"/>
|
||||
</XColumn>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, shallowRef } from 'vue';
|
||||
import type { Column } from '@/ui/deck/deck-store.js';
|
||||
import type { FollowingFeedState } from '@/scripts/following-feed-utils.js';
|
||||
export type FollowingColumn = Column & Partial<FollowingFeedState>;
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getColumn, getReactiveColumn, updateColumn } from '@/ui/deck/deck-store.js';
|
||||
import XColumn from '@/ui/deck/column.vue';
|
||||
import SkFollowingRecentNotes from '@/components/SkFollowingRecentNotes.vue';
|
||||
import SkRemoteFollowersWarning from '@/components/SkRemoteFollowersWarning.vue';
|
||||
import { createModel, createOptionsMenu, FollowingFeedTab, followingTab, followingTabName, followingTabIcon, followingFeedTabs } from '@/scripts/following-feed-utils.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import { useRouter } from '@/router/supplier.js';
|
||||
|
||||
const props = defineProps<{
|
||||
column: FollowingColumn;
|
||||
isStacked: boolean;
|
||||
}>();
|
||||
|
||||
const columnIcon = computed(() => followingTabIcon(props.column.userList));
|
||||
|
||||
async function selectList(): Promise<void> {
|
||||
const { canceled, result: newList } = await os.select<FollowingFeedTab>({
|
||||
title: i18n.ts.selectFollowRelationship,
|
||||
items: followingFeedTabs.map(t => ({
|
||||
value: t,
|
||||
text: followingTabName(t),
|
||||
})),
|
||||
default: props.column.userList ?? followingTab,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
await updateColumn(props.column.id, {
|
||||
name: getNewColumnName(newList),
|
||||
userList: newList,
|
||||
});
|
||||
}
|
||||
|
||||
function getNewColumnName(newList: FollowingFeedTab) {
|
||||
// If the user has renamed the column, then we need to keep that name.
|
||||
// If no list is specified, then the column is newly created and the user *can't* have renamed it.
|
||||
if (props.column.userList && props.column.name === followingTabName(props.column.userList)) {
|
||||
return props.column.name;
|
||||
}
|
||||
|
||||
// Otherwise, we should match the name to the selected list.
|
||||
return followingTabName(newList);
|
||||
}
|
||||
|
||||
if (!props.column.userList) {
|
||||
await selectList();
|
||||
}
|
||||
|
||||
// Redirects the Following Feed logic into column-specific storage.
|
||||
// This allows multiple columns to exist with different settings.
|
||||
const columnStorage = computed(() => ({
|
||||
state: getColumn<FollowingColumn>(props.column.id),
|
||||
reactiveState: getReactiveColumn<FollowingColumn>(props.column.id),
|
||||
save(updated: FollowingColumn) {
|
||||
updateColumn(props.column.id, updated);
|
||||
},
|
||||
}));
|
||||
|
||||
const model = createModel(columnStorage);
|
||||
const {
|
||||
userList,
|
||||
withNonPublic,
|
||||
withQuotes,
|
||||
withReplies,
|
||||
withBots,
|
||||
onlyFiles,
|
||||
} = model;
|
||||
|
||||
const menu: MenuItem[] = [
|
||||
{
|
||||
icon: columnIcon.value,
|
||||
text: i18n.ts.selectFollowRelationship,
|
||||
action: selectList,
|
||||
},
|
||||
...createOptionsMenu(columnStorage),
|
||||
];
|
||||
|
||||
const latestNotes = shallowRef<InstanceType<typeof SkFollowingRecentNotes>>();
|
||||
|
||||
async function reload() {
|
||||
await latestNotes.value?.reload();
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function userSelected(userId: string) {
|
||||
router.push(`/following-feed/${userId}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.followersWarning {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue