feat: メディアタイムラインを輸入 (#103)
* メディアタイムラインの実装 * refactor(stream): ストリーミングにwithFilesオプションを実装 --------- Co-authored-by: tar_bin <tar.bin.master@gmail.com>
This commit is contained in:
parent
bbd5282dc6
commit
1516de1fee
|
@ -1933,6 +1933,7 @@ _instanceCharts:
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "Home"
|
home: "Home"
|
||||||
local: "Local"
|
local: "Local"
|
||||||
|
media: "Media"
|
||||||
social: "Social"
|
social: "Social"
|
||||||
global: "Global"
|
global: "Global"
|
||||||
_play:
|
_play:
|
||||||
|
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
|
@ -2067,6 +2067,7 @@ export interface Locale {
|
||||||
"_timelines": {
|
"_timelines": {
|
||||||
"home": string;
|
"home": string;
|
||||||
"local": string;
|
"local": string;
|
||||||
|
"media": string;
|
||||||
"social": string;
|
"social": string;
|
||||||
"global": string;
|
"global": string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1982,6 +1982,7 @@ _instanceCharts:
|
||||||
_timelines:
|
_timelines:
|
||||||
home: "ホーム"
|
home: "ホーム"
|
||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
|
media: "メディア"
|
||||||
social: "ソーシャル"
|
social: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0179793ec891856d6f37a3be16ba4c22f67a81b5
|
Subproject commit cf3ce27b2eb8417233072e3d6d2fb7c5356c2364
|
|
@ -19,6 +19,7 @@ class GlobalTimelineChannel extends Channel {
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
private withReplies: boolean;
|
private withReplies: boolean;
|
||||||
|
private withFiles: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
@ -38,6 +39,7 @@ class GlobalTimelineChannel extends Channel {
|
||||||
if (!policies.gtlAvailable) return;
|
if (!policies.gtlAvailable) return;
|
||||||
|
|
||||||
this.withReplies = params.withReplies as boolean;
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
this.withFiles = params.withFiles as boolean;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
@ -48,6 +50,9 @@ class GlobalTimelineChannel extends Channel {
|
||||||
if (note.visibility !== 'public') return;
|
if (note.visibility !== 'public') return;
|
||||||
if (note.channelId != null) return;
|
if (note.channelId != null) return;
|
||||||
|
|
||||||
|
// ファイルを含まない投稿は除外
|
||||||
|
if (this.withFiles && (note.files === undefined || note.files.length === 0)) return;
|
||||||
|
|
||||||
// リプライなら再pack
|
// リプライなら再pack
|
||||||
if (note.replyId != null) {
|
if (note.replyId != null) {
|
||||||
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
|
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
|
||||||
|
|
|
@ -17,6 +17,7 @@ class HomeTimelineChannel extends Channel {
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = true;
|
public static requireCredential = true;
|
||||||
private withReplies: boolean;
|
private withReplies: boolean;
|
||||||
|
private withFiles: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
|
@ -31,6 +32,7 @@ class HomeTimelineChannel extends Channel {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async init(params: any) {
|
public async init(params: any) {
|
||||||
this.withReplies = params.withReplies as boolean;
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
this.withFiles = params.withFiles as boolean;
|
||||||
|
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
}
|
}
|
||||||
|
@ -47,6 +49,9 @@ class HomeTimelineChannel extends Channel {
|
||||||
// Ignore notes from instances the user has muted
|
// Ignore notes from instances the user has muted
|
||||||
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
||||||
|
|
||||||
|
// ファイルを含まない投稿は除外
|
||||||
|
if (this.withFiles && (note.files === undefined || note.files.length === 0)) return;
|
||||||
|
|
||||||
if (['followers', 'specified'].includes(note.visibility)) {
|
if (['followers', 'specified'].includes(note.visibility)) {
|
||||||
note = await this.noteEntityService.pack(note.id, this.user!, {
|
note = await this.noteEntityService.pack(note.id, this.user!, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
|
|
@ -19,6 +19,7 @@ class HybridTimelineChannel extends Channel {
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = true;
|
public static requireCredential = true;
|
||||||
private withReplies: boolean;
|
private withReplies: boolean;
|
||||||
|
private withFiles: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
@ -38,6 +39,7 @@ class HybridTimelineChannel extends Channel {
|
||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
this.withReplies = params.withReplies as boolean;
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
this.withFiles = params.withFiles as boolean;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
@ -56,6 +58,9 @@ class HybridTimelineChannel extends Channel {
|
||||||
(note.channelId != null && this.followingChannels.has(note.channelId))
|
(note.channelId != null && this.followingChannels.has(note.channelId))
|
||||||
)) return;
|
)) return;
|
||||||
|
|
||||||
|
// ファイルを含まない投稿は除外
|
||||||
|
if (this.withFiles && (note.files === undefined || note.files.length === 0)) return;
|
||||||
|
|
||||||
if (['followers', 'specified'].includes(note.visibility)) {
|
if (['followers', 'specified'].includes(note.visibility)) {
|
||||||
note = await this.noteEntityService.pack(note.id, this.user!, {
|
note = await this.noteEntityService.pack(note.id, this.user!, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
|
|
@ -18,6 +18,7 @@ class LocalTimelineChannel extends Channel {
|
||||||
public static shouldShare = true;
|
public static shouldShare = true;
|
||||||
public static requireCredential = false;
|
public static requireCredential = false;
|
||||||
private withReplies: boolean;
|
private withReplies: boolean;
|
||||||
|
private withFiles: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
@ -37,6 +38,7 @@ class LocalTimelineChannel extends Channel {
|
||||||
if (!policies.ltlAvailable) return;
|
if (!policies.ltlAvailable) return;
|
||||||
|
|
||||||
this.withReplies = params.withReplies as boolean;
|
this.withReplies = params.withReplies as boolean;
|
||||||
|
this.withFiles = params.withFiles as boolean;
|
||||||
|
|
||||||
// Subscribe events
|
// Subscribe events
|
||||||
this.subscriber.on('notesStream', this.onNote);
|
this.subscriber.on('notesStream', this.onNote);
|
||||||
|
@ -48,6 +50,9 @@ class LocalTimelineChannel extends Channel {
|
||||||
if (note.visibility !== 'public') return;
|
if (note.visibility !== 'public') return;
|
||||||
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
|
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
|
||||||
|
|
||||||
|
// ファイルを含まない投稿は除外
|
||||||
|
if (this.withFiles && (note.files === undefined || note.files.length === 0)) return;
|
||||||
|
|
||||||
// リプライなら再pack
|
// リプライなら再pack
|
||||||
if (note.replyId != null) {
|
if (note.replyId != null) {
|
||||||
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
|
note.reply = await this.noteEntityService.pack(note.replyId, this.user, {
|
||||||
|
|
|
@ -79,6 +79,17 @@ if (props.src === 'antenna') {
|
||||||
withReplies: defaultStore.state.showTimelineReplies,
|
withReplies: defaultStore.state.showTimelineReplies,
|
||||||
});
|
});
|
||||||
connection.on('note', prepend);
|
connection.on('note', prepend);
|
||||||
|
} else if (props.src === 'media') {
|
||||||
|
endpoint = 'notes/hybrid-timeline';
|
||||||
|
query = {
|
||||||
|
withFiles: true,
|
||||||
|
withReplies: defaultStore.state.showTimelineReplies,
|
||||||
|
};
|
||||||
|
connection = stream.useChannel('hybridTimeline', {
|
||||||
|
withFiles: true,
|
||||||
|
withReplies: defaultStore.state.showTimelineReplies,
|
||||||
|
});
|
||||||
|
connection.on('note', prepend);
|
||||||
} else if (props.src === 'social') {
|
} else if (props.src === 'social') {
|
||||||
endpoint = 'notes/hybrid-timeline';
|
endpoint = 'notes/hybrid-timeline';
|
||||||
query = {
|
query = {
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
||||||
|
@ -38,8 +33,6 @@ import { i18n } from '@/i18n';
|
||||||
import { instance } from '@/instance';
|
import { instance } from '@/instance';
|
||||||
import { $i } from '@/account';
|
import { $i } from '@/account';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
import { miLocalStorage } from '@/local-storage';
|
|
||||||
import { antennasCache, userListsCache } from '@/cache';
|
|
||||||
|
|
||||||
provide('shouldOmitHeaderTitle', true);
|
provide('shouldOmitHeaderTitle', true);
|
||||||
|
|
||||||
|
@ -47,7 +40,6 @@ const XTutorial = defineAsyncComponent(() => import('./timeline.tutorial.vue'));
|
||||||
|
|
||||||
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
const isLocalTimelineAvailable = ($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable);
|
||||||
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
|
const isGlobalTimelineAvailable = ($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable);
|
||||||
const isAdmin = ($i != null && $i.isAdmin);
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
't': focus,
|
't': focus,
|
||||||
};
|
};
|
||||||
|
@ -70,7 +62,7 @@ function top(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||||
const lists = await userListsCache.fetch();
|
const lists = await os.api('users/lists/list');
|
||||||
const items = lists.map(list => ({
|
const items = lists.map(list => ({
|
||||||
type: 'link' as const,
|
type: 'link' as const,
|
||||||
text: list.name,
|
text: list.name,
|
||||||
|
@ -80,7 +72,7 @@ async function chooseList(ev: MouseEvent): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
async function chooseAntenna(ev: MouseEvent): Promise<void> {
|
||||||
const antennas = await antennasCache.fetch();
|
const antennas = await os.api('antennas/list');
|
||||||
const items = antennas.map(antenna => ({
|
const items = antennas.map(antenna => ({
|
||||||
type: 'link' as const,
|
type: 'link' as const,
|
||||||
text: antenna.name,
|
text: antenna.name,
|
||||||
|
@ -103,7 +95,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
|
||||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | 'all'): void {
|
function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global'): void {
|
||||||
defaultStore.set('tl', {
|
defaultStore.set('tl', {
|
||||||
...defaultStore.state.tl,
|
...defaultStore.state.tl,
|
||||||
src: newSrc,
|
src: newSrc,
|
||||||
|
@ -137,10 +129,10 @@ const headerTabs = $computed(() => [{
|
||||||
icon: 'ti ti-planet',
|
icon: 'ti ti-planet',
|
||||||
iconOnly: true,
|
iconOnly: true,
|
||||||
}, {
|
}, {
|
||||||
key: 'media',
|
key: 'media',
|
||||||
title: i18n.ts._timelines.media,
|
title: i18n.ts._timelines.media,
|
||||||
icon: 'ti ti-photo',
|
icon: 'ti ti-photo',
|
||||||
iconOnly: true,
|
iconOnly: true,
|
||||||
}, {
|
}, {
|
||||||
key: 'social',
|
key: 'social',
|
||||||
title: i18n.ts._timelines.social,
|
title: i18n.ts._timelines.social,
|
||||||
|
@ -151,11 +143,6 @@ const headerTabs = $computed(() => [{
|
||||||
title: i18n.ts._timelines.global,
|
title: i18n.ts._timelines.global,
|
||||||
icon: 'ti ti-whirl',
|
icon: 'ti ti-whirl',
|
||||||
iconOnly: true,
|
iconOnly: true,
|
||||||
}] : []), ...(isAdmin ? [{
|
|
||||||
key: 'all',
|
|
||||||
title: 'all',
|
|
||||||
icon: 'ti ti-whirl',
|
|
||||||
iconOnly: true,
|
|
||||||
}] : []), {
|
}] : []), {
|
||||||
icon: 'ti ti-list',
|
icon: 'ti ti-list',
|
||||||
title: i18n.ts.lists,
|
title: i18n.ts.lists,
|
||||||
|
|
|
@ -29,7 +29,7 @@ export type Column = {
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
includingTypes?: typeof notificationTypes[number][];
|
includingTypes?: typeof notificationTypes[number][];
|
||||||
tl?: 'home' | 'local' | 'social' | 'global';
|
tl?: 'home' | 'local' | 'media' | 'social' | 'global';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deckStore = markRaw(new Storage('deck', {
|
export const deckStore = markRaw(new Storage('deck', {
|
||||||
|
|
|
@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i v-if="column.tl === 'home'" class="ti ti-home"></i>
|
<i v-if="column.tl === 'home'" class="ti ti-home"></i>
|
||||||
<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
|
<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
|
||||||
<i v-else-if="column.tl === 'social'" class="ti ti-rocket"></i>
|
<i v-else-if="column.tl === 'social'" class="ti ti-rocket"></i>
|
||||||
|
<i v-else-if="column.tl === 'media'" class="ti ti-photo"></i>
|
||||||
<i v-else-if="column.tl === 'global'" class="ti ti-whirl"></i>
|
<i v-else-if="column.tl === 'global'" class="ti ti-whirl"></i>
|
||||||
<span style="margin-left: 8px;">{{ column.name }}</span>
|
<span style="margin-left: 8px;">{{ column.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -61,6 +62,8 @@ async function setType() {
|
||||||
value: 'home' as const, text: i18n.ts._timelines.home,
|
value: 'home' as const, text: i18n.ts._timelines.home,
|
||||||
}, {
|
}, {
|
||||||
value: 'local' as const, text: i18n.ts._timelines.local,
|
value: 'local' as const, text: i18n.ts._timelines.local,
|
||||||
|
}, {
|
||||||
|
value: 'media' as const, text: i18n.ts._timelines.media,
|
||||||
}, {
|
}, {
|
||||||
value: 'social' as const, text: i18n.ts._timelines.social,
|
value: 'social' as const, text: i18n.ts._timelines.social,
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -122,6 +122,10 @@ const choose = async (ev) => {
|
||||||
text: i18n.ts._timelines.local,
|
text: i18n.ts._timelines.local,
|
||||||
icon: 'ti ti-planet',
|
icon: 'ti ti-planet',
|
||||||
action: () => { setSrc('local'); },
|
action: () => { setSrc('local'); },
|
||||||
|
}, {
|
||||||
|
text: i18n.ts._timelines.media,
|
||||||
|
icon: 'ti ti-photo',
|
||||||
|
action: () => { setSrc('media'); },
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts._timelines.social,
|
text: i18n.ts._timelines.social,
|
||||||
icon: 'ti ti-rocket',
|
icon: 'ti ti-rocket',
|
||||||
|
|
Loading…
Reference in a new issue