This commit is contained in:
syuilo 2025-08-26 13:58:57 +09:00
parent d6a1046361
commit 05cc8047fa
6 changed files with 50 additions and 29 deletions

View File

@ -297,76 +297,97 @@ function prepend(note: Misskey.entities.Note & MisskeyEntity) {
}
}
let connection: Misskey.IChannelConnection | null = null;
let connection2: Misskey.IChannelConnection | null = null;
const stream = store.s.realtimeMode ? useStream() : null;
const connections = {
antenna: null as Misskey.IChannelConnection<Misskey.Channels['antenna']> | null,
homeTimeline: null as Misskey.IChannelConnection<Misskey.Channels['homeTimeline']> | null,
localTimeline: null as Misskey.IChannelConnection<Misskey.Channels['localTimeline']> | null,
hybridTimeline: null as Misskey.IChannelConnection<Misskey.Channels['hybridTimeline']> | null,
globalTimeline: null as Misskey.IChannelConnection<Misskey.Channels['globalTimeline']> | null,
main: null as Misskey.IChannelConnection<Misskey.Channels['main']> | null,
userList: null as Misskey.IChannelConnection<Misskey.Channels['userList']> | null,
channel: null as Misskey.IChannelConnection<Misskey.Channels['channel']> | null,
roleTimeline: null as Misskey.IChannelConnection<Misskey.Channels['roleTimeline']> | null,
};
function connectChannel() {
if (stream == null) return;
if (props.src === 'antenna') {
if (props.antenna == null) return;
connection = stream.useChannel('antenna', {
connections.antenna = stream.useChannel('antenna', {
antennaId: props.antenna,
});
connections.antenna.on('note', prepend);
} else if (props.src === 'home') {
connection = stream.useChannel('homeTimeline', {
connections.homeTimeline = stream.useChannel('homeTimeline', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
});
connection2 = stream.useChannel('main');
connections.main = stream.useChannel('main');
connections.homeTimeline.on('note', prepend);
} else if (props.src === 'local') {
connection = stream.useChannel('localTimeline', {
connections.localTimeline = stream.useChannel('localTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
});
connections.localTimeline.on('note', prepend);
} else if (props.src === 'social') {
connection = stream.useChannel('hybridTimeline', {
connections.hybridTimeline = stream.useChannel('hybridTimeline', {
withRenotes: props.withRenotes,
withReplies: props.withReplies,
withFiles: props.onlyFiles ? true : undefined,
});
connections.hybridTimeline.on('note', prepend);
} else if (props.src === 'global') {
connection = stream.useChannel('globalTimeline', {
connections.globalTimeline = stream.useChannel('globalTimeline', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
});
connections.globalTimeline.on('note', prepend);
} else if (props.src === 'mentions') {
connection = stream.useChannel('main');
connection.on('mention', prepend);
connections.main = stream.useChannel('main');
connections.main.on('mention', prepend);
} else if (props.src === 'directs') {
const onNote = note => {
if (note.visibility === 'specified') {
prepend(note);
}
};
connection = stream.useChannel('main');
connection.on('mention', onNote);
connections.main = stream.useChannel('main');
connections.main.on('mention', onNote);
} else if (props.src === 'list') {
if (props.list == null) return;
connection = stream.useChannel('userList', {
connections.userList = stream.useChannel('userList', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
listId: props.list,
});
connections.userList.on('note', prepend);
} else if (props.src === 'channel') {
if (props.channel == null) return;
connection = stream.useChannel('channel', {
connections.channel = stream.useChannel('channel', {
channelId: props.channel,
});
connections.channel.on('note', prepend);
} else if (props.src === 'role') {
if (props.role == null) return;
connection = stream.useChannel('roleTimeline', {
connections.roleTimeline = stream.useChannel('roleTimeline', {
roleId: props.role,
});
connections.roleTimeline.on('note', prepend);
}
if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
}
function disconnectChannel() {
if (connection) connection.dispose();
if (connection2) connection2.dispose();
for (const key in connections) {
const conn = connections[key as keyof typeof connections];
if (conn != null) {
conn.dispose();
connections[key as keyof typeof connections] = null;
}
}
}
if (store.s.realtimeMode) {

View File

@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFukidashi>
</div>
<div v-if="user.roles.length > 0" class="roles">
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color ?? '' }">
<MkA v-adaptive-bg :to="`/roles/${role.id}`">
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
{{ role.name }}
@ -249,7 +249,7 @@ const style = computed(() => {
});
const age = computed(() => {
return calcAge(props.user.birthday);
return props.user.birthday ? calcAge(props.user.birthday) : NaN;
});
function menu(ev: MouseEvent) {

View File

@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.body">
<div :class="$style.top">
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
<img :src="instance.iconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
</button>
<button v-if="!iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
<i class="ti ti-bolt ti-fw"></i>

View File

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
mode="default"
>
<MkMarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
<span v-for="instance in instances" :key="instance.id" :class="[$style.item, { [$style.colored]: colored }]" :style="{ background: colored ? instance.themeColor : null }">
<span v-for="instance in instances" :key="instance.id" :class="[$style.item, { [$style.colored]: colored }]" :style="{ background: colored ? instance.themeColor : '' }">
<img :class="$style.icon" :src="getInstanceIcon(instance)" alt=""/>
<MkA :to="`/instance-info/${instance.host}`" :class="$style.host" class="_monospace">
{{ instance.host }}
@ -33,9 +33,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
import MkMarqueeText from '@/components/MkMarqueeText.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
const props = defineProps<{
@ -44,7 +44,7 @@ const props = defineProps<{
marqueeDuration?: number;
marqueeReverse?: boolean;
oneByOneInterval?: number;
refreshIntervalSec?: number;
refreshIntervalSec: number;
}>();
const instances = ref<Misskey.entities.FederationInstance[]>([]);

View File

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<MkMarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
<span v-for="note in notes" :key="note.id" :class="$style.item">
<img :class="$style.avatar" :src="note.user.avatarUrl" decoding="async"/>
<img v-if="note.user.avatarUrl" :class="$style.avatar" :src="note.user.avatarUrl" decoding="async"/>
<MkA :class="$style.text" :to="notePage(note)">
<Mfm :text="getNoteSummary(note)" :plain="true" :nowrap="true"/>
</MkA>
@ -33,9 +33,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useInterval } from '@@/js/use-interval.js';
import MkMarqueeText from '@/components/MkMarqueeText.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { useInterval } from '@@/js/use-interval.js';
import { getNoteSummary } from '@/utility/get-note-summary.js';
import { notePage } from '@/filters/note.js';
@ -45,7 +45,7 @@ const props = defineProps<{
marqueeDuration?: number;
marqueeReverse?: boolean;
oneByOneInterval?: number;
refreshIntervalSec?: number;
refreshIntervalSec: number;
}>();
const notes = ref<Misskey.entities.Note[]>([]);

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<div :class="$style.title">
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
<img :src="instance.iconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
<span :class="$style.instanceTitle">{{ instance.name ?? host }}</span>
</div>
<div :class="$style.controls">