feat(client): add channel column to deck

This commit is contained in:
syuilo 2023-02-09 10:35:28 +09:00
parent d195b0dec7
commit 7afee5977f
10 changed files with 110 additions and 24 deletions

View file

@ -8,6 +8,15 @@
You should also include the user name that made the change. You should also include the user name that made the change.
--> -->
## 13.x.x (unreleased)
### Improvements
- Client: デッキにチャンネルカラムを追加
### Bugfixes
-
## 13.5.2 (2023/02/08) ## 13.5.2 (2023/02/08)
### Changes ### Changes

View file

@ -129,6 +129,7 @@ unblockConfirm: "ブロック解除しますか?"
suspendConfirm: "凍結しますか?" suspendConfirm: "凍結しますか?"
unsuspendConfirm: "解凍しますか?" unsuspendConfirm: "解凍しますか?"
selectList: "リストを選択" selectList: "リストを選択"
selectChannel: "チャンネルを選択"
selectAntenna: "アンテナを選択" selectAntenna: "アンテナを選択"
selectWidget: "ウィジェットを選択" selectWidget: "ウィジェットを選択"
editWidgets: "ウィジェットを編集" editWidgets: "ウィジェットを編集"
@ -1922,5 +1923,6 @@ _deck:
tl: "タイムライン" tl: "タイムライン"
antenna: "アンテナ" antenna: "アンテナ"
list: "リスト" list: "リスト"
channel: "チャンネル"
mentions: "あなた宛て" mentions: "あなた宛て"
direct: "ダイレクト" direct: "ダイレクト"

View file

@ -23,7 +23,7 @@
</div> </div>
</div> </div>
<XPostForm v-if="$i" :channel="channel" class="post-form _panel _margin" fixed/> <MkPostForm v-if="$i" :channel="channel" class="post-form _panel _margin" fixed/>
<XTimeline :key="channelId" class="_margin" src="channel" :channel="channelId" @before="before" @after="after"/> <XTimeline :key="channelId" class="_margin" src="channel" :channel="channelId" @before="before" @after="after"/>
</div> </div>
@ -34,7 +34,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, watch } from 'vue'; import { computed, inject, watch } from 'vue';
import MkContainer from '@/components/MkContainer.vue'; import MkContainer from '@/components/MkContainer.vue';
import XPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import XTimeline from '@/components/MkTimeline.vue'; import XTimeline from '@/components/MkTimeline.vue';
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
import * as os from '@/os'; import * as os from '@/os';

View file

@ -29,7 +29,7 @@ import { noteVisibilities } from 'misskey-js';
import * as Acct from 'misskey-js/built/acct'; import * as Acct from 'misskey-js/built/acct';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import XPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import * as os from '@/os'; import * as os from '@/os';
import { mainRouter } from '@/router'; import { mainRouter } from '@/router';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
@ -69,14 +69,14 @@ async function init() {
...(visibleAccts ? visibleAccts.split(',').map(Acct.parse) : []), ...(visibleAccts ? visibleAccts.split(',').map(Acct.parse) : []),
] ]
// TypeScript // TypeScript
.map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q) .map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q)
.map(q => os.api('users/show', q) .map(q => os.api('users/show', q)
.then(user => { .then(user => {
visibleUsers.push(user); visibleUsers.push(user);
}, () => { }, () => {
console.error(`Invalid user query: ${JSON.stringify(q)}`); console.error(`Invalid user query: ${JSON.stringify(q)}`);
}), }),
), ),
); );
} }
@ -120,13 +120,13 @@ async function init() {
if (fileIds) { if (fileIds) {
await Promise.all( await Promise.all(
fileIds.split(',') fileIds.split(',')
.map(fileId => os.api('drive/files/show', { fileId }) .map(fileId => os.api('drive/files/show', { fileId })
.then(file => { .then(file => {
files.push(file); files.push(file);
}, () => { }, () => {
console.error(`Failed to fetch a file ${fileId}`); console.error(`Failed to fetch a file ${fileId}`);
}), }),
), ),
); );
} }
//#endregion //#endregion

View file

@ -4,7 +4,7 @@
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<div ref="rootEl" v-hotkey.global="keymap"> <div ref="rootEl" v-hotkey.global="keymap">
<XTutorial v-if="$i && $store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/> <XTutorial v-if="$i && $store.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/> <MkPostForm v-if="$store.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<div :class="$style.tl"> <div :class="$style.tl">
@ -24,7 +24,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, computed, watch } from 'vue'; import { defineAsyncComponent, computed, watch } from 'vue';
import XTimeline from '@/components/MkTimeline.vue'; import XTimeline from '@/components/MkTimeline.vue';
import XPostForm from '@/components/MkPostForm.vue'; import MkPostForm from '@/components/MkPostForm.vue';
import { scroll } from '@/scripts/scroll'; import { scroll } from '@/scripts/scroll';
import * as os from '@/os'; import * as os from '@/os';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';

View file

@ -146,6 +146,7 @@ const addColumn = async (ev) => {
'tl', 'tl',
'antenna', 'antenna',
'list', 'list',
'channel',
'mentions', 'mentions',
'direct', 'direct',
]; ];

View file

@ -0,0 +1,71 @@
<template>
<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
<template #header>
<i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template>
<template v-if="column.channelId">
<div style="padding: 8px; text-align: center;">
<MkButton primary gradate rounded inline @click="post"><i class="ti ti-pencil"></i></MkButton>
</div>
<XTimeline ref="timeline" src="channel" :channel="column.channelId" @after="() => emit('loaded')"/>
</template>
</XColumn>
</template>
<script lang="ts" setup>
import { } from 'vue';
import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store';
import XTimeline from '@/components/MkTimeline.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
const props = defineProps<{
column: Column;
isStacked: boolean;
}>();
const emit = defineEmits<{
(ev: 'loaded'): void;
(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
}>();
let timeline = $shallowRef<InstanceType<typeof XTimeline>>();
if (props.column.channelId == null) {
setChannel();
}
async function setChannel() {
const channels = await os.api('channels/followed');
const { canceled, result: channel } = await os.select({
title: i18n.ts.selectChannel,
items: channels.map(x => ({
value: x, text: x.name,
})),
default: props.column.channelId,
});
if (canceled) return;
updateColumn(props.column.id, {
channelId: channel.id,
name: channel.name,
});
}
function post() {
os.post({
channel: {
id: props.column.channelId,
},
instant: true,
});
}
const menu = [{
icon: 'ti ti-pencil',
text: i18n.ts.selectChannel,
action: setChannel,
}];
</script>

View file

@ -6,6 +6,7 @@
<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XChannelColumn v-else-if="column.type === 'channel'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> <XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
@ -17,6 +18,7 @@ import XMainColumn from './main-column.vue';
import XTlColumn from './tl-column.vue'; import XTlColumn from './tl-column.vue';
import XAntennaColumn from './antenna-column.vue'; import XAntennaColumn from './antenna-column.vue';
import XListColumn from './list-column.vue'; import XListColumn from './list-column.vue';
import XChannelColumn from './channel-column.vue';
import XNotificationsColumn from './notifications-column.vue'; import XNotificationsColumn from './notifications-column.vue';
import XWidgetsColumn from './widgets-column.vue'; import XWidgetsColumn from './widgets-column.vue';
import XMentionsColumn from './mentions-column.vue'; import XMentionsColumn from './mentions-column.vue';

View file

@ -14,7 +14,7 @@ type ColumnWidget = {
export type Column = { export type Column = {
id: string; id: string;
type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'list' | 'mentions' | 'direct'; type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'channel' | 'list' | 'mentions' | 'direct';
name: string | null; name: string | null;
width: number; width: number;
widgets?: ColumnWidget[]; widgets?: ColumnWidget[];
@ -22,6 +22,7 @@ export type Column = {
flexible?: boolean; flexible?: boolean;
antennaId?: string; antennaId?: string;
listId?: string; listId?: string;
channelId?: string;
includingTypes?: typeof notificationTypes[number][]; includingTypes?: typeof notificationTypes[number][];
tl?: 'home' | 'local' | 'social' | 'global'; tl?: 'home' | 'local' | 'social' | 'global';
}; };

View file

@ -1,12 +1,12 @@
<template> <template>
<XPostForm class="_panel mkw-postForm data-cy-mkw-postForm" :fixed="true" :autofocus="false"/> <MkPostForm class="_panel mkw-post-form data-cy-mkw-postForm" :fixed="true" :autofocus="false"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { } from 'vue'; import { } from 'vue';
import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
import XPostForm from '@/components/MkPostForm.vue'; import { GetFormResultType } from '@/scripts/form';
import MkPostForm from '@/components/MkPostForm.vue';
const name = 'postForm'; const name = 'postForm';