feat: リストをピン留めできるように

This commit is contained in:
mattyatea 2024-06-11 17:38:38 +09:00
parent 184890154f
commit 8bfc9f1165
2 changed files with 36 additions and 96 deletions

View File

@ -1,33 +1,29 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template> <template>
<div class="_gaps_m"> <div class="_gaps_m">
<FormSlot> <FormSlot>
<template #label>{{ i18n.ts.timelineHeader }}</template> <template #label>{{ i18n.ts.timelineHeader }}</template>
<MkContainer :showHeader="false"> <MkContainer :showHeader="false">
<Sortable <div style="overflow-x: auto;">
v-model="items" <Sortable
itemKey="id" v-model="items"
:animation="150" itemKey="id"
:handle="'.' + $style.itemHandle" :class="$style.container"
@start="e => e.item.classList.add('active')" :animation="150"
@end="e => e.item.classList.remove('active')" :handle="'.' + $style.itemHandle"
> @start="e => e.item.classList.add('active')"
<template #item="{element,index}"> @end="e => e.item.classList.remove('active')"
<div >
v-if="element.type === '-' || timelineHeaderItemDef[element.type]" <template #item="{element,index}">
:class="$style.item" <div
> :class="$style.item"
<button class="_button" :class="$style.itemHandle"><i class="ti ti-menu"></i></button> >
<button class="_button" :class="$style.itemHandle"><i class="ti ti-menu"></i></button>
<i class="ti-fw" :class="[$style.itemIcon, timelineHeaderItemDef[element.type]?.icon]"></i><span :class="$style.itemText">{{ timelineHeaderItemDef[element.type]?.title }}</span> <i class="ti-fw" :class="[$style.itemIcon, timelineHeaderItemDef[element.type]?.icon]"></i><span :class="$style.itemText">{{ timelineHeaderItemDef[element.type]?.title }}</span>
<button class="_button" :class="$style.itemRemove" @click="removeItem(index)"><i class="ti ti-x"></i></button> <button class="_button" :class="$style.itemRemove" @click="removeItem(index)"><i class="ti ti-x"></i></button>
</div> </div>
</template> </template>
</Sortable> </Sortable>
</div>
</MkContainer> </MkContainer>
</FormSlot> </FormSlot>
<div class="_buttons"> <div class="_buttons">
@ -116,6 +112,7 @@ definePageMetadata(() => ({
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
color: var(--navFg); color: var(--navFg);
min-width: 200px;
} }
.itemIcon { .itemIcon {
@ -146,4 +143,8 @@ definePageMetadata(() => ({
margin: 0 8px; margin: 0 8px;
opacity: 0.5; opacity: 0.5;
} }
.container{
display: flex;
}
</style> </style>

View File

@ -5,12 +5,9 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { antennasCache, favoritedChannelsCache, userListsCache } from '@/cache.js'; import { userListsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
import { miLocalStorage } from '@/local-storage.js';
import { isLocalTimelineAvailable, isGlobalTimelineAvailable } from '@/store.js'; import { isLocalTimelineAvailable, isGlobalTimelineAvailable } from '@/store.js';
const lists = await userListsCache.fetch();
export const timelineHeaderItemDef = reactive({ export const timelineHeaderItemDef = reactive({
home: { home: {
title: i18n.ts._timelines.home, title: i18n.ts._timelines.home,
@ -19,7 +16,6 @@ export const timelineHeaderItemDef = reactive({
}, },
...(isLocalTimelineAvailable ? { ...(isLocalTimelineAvailable ? {
local: { local: {
key: 'local',
title: i18n.ts._timelines.local, title: i18n.ts._timelines.local,
icon: 'ti ti-planet', icon: 'ti ti-planet',
iconOnly: true, iconOnly: true,
@ -30,7 +26,6 @@ export const timelineHeaderItemDef = reactive({
iconOnly: true, iconOnly: true,
} } : {}), } } : {}),
...(isGlobalTimelineAvailable ? { global: { ...(isGlobalTimelineAvailable ? { global: {
key: 'global',
title: i18n.ts._timelines.global, title: i18n.ts._timelines.global,
icon: 'ti ti-whirl', icon: 'ti ti-whirl',
iconOnly: true, iconOnly: true,
@ -50,68 +45,12 @@ export const timelineHeaderItemDef = reactive({
title: i18n.ts.channel, title: i18n.ts.channel,
iconOnly: true, iconOnly: true,
}, },
...lists.reduce((acc, l) => {
acc['list:' + l.id] = {
title: i18n.ts.lists + ':' + l.name,
icon: 'ti ti-star',
iconOnly: true,
};
return acc;
}, {}),
}); });
async function chooseList(ev: MouseEvent): Promise<void> {
const lists = await userListsCache.fetch();
const items: MenuItem[] = [
...lists.map(list => ({
type: 'link' as const,
text: list.name,
to: `/timeline/list/${list.id}`,
})),
(lists.length === 0 ? undefined : { type: 'divider' }),
{
type: 'link' as const,
icon: 'ti ti-plus',
text: i18n.ts.createNew,
to: '/my/lists',
},
];
os.popupMenu(items, ev.currentTarget ?? ev.target);
}
async function chooseAntenna(ev: MouseEvent): Promise<void> {
const antennas = await antennasCache.fetch();
const items: MenuItem[] = [
...antennas.map(antenna => ({
type: 'link' as const,
text: antenna.name,
indicate: antenna.hasUnreadNote,
to: `/timeline/antenna/${antenna.id}`,
})),
(antennas.length === 0 ? undefined : { type: 'divider' }),
{
type: 'link' as const,
icon: 'ti ti-plus',
text: i18n.ts.createNew,
to: '/my/antennas',
},
];
os.popupMenu(items, ev.currentTarget ?? ev.target);
}
async function chooseChannel(ev: MouseEvent): Promise<void> {
const channels = await favoritedChannelsCache.fetch();
const items: MenuItem[] = [
...channels.map(channel => {
const lastReadedAt = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.id}`) ?? null;
const hasUnreadNote = (lastReadedAt && channel.lastNotedAt) ? Date.parse(channel.lastNotedAt) > lastReadedAt : !!(!lastReadedAt && channel.lastNotedAt);
return {
type: 'link' as const,
text: channel.name,
indicate: hasUnreadNote,
to: `/channels/${channel.id}`,
};
}),
(channels.length === 0 ? undefined : { type: 'divider' }),
{
type: 'link' as const,
icon: 'ti ti-plus',
text: i18n.ts.createNew,
to: '/channels',
},
];
os.popupMenu(items, ev.currentTarget ?? ev.target);
}