diff --git a/packages/backend/src/server/api/stream/channels/chat.ts b/packages/backend/src/server/api/stream/channels/chat.ts index 3701927275..ac5d7c73b3 100644 --- a/packages/backend/src/server/api/stream/channels/chat.ts +++ b/packages/backend/src/server/api/stream/channels/chat.ts @@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { JsonObject } from '@/misc/json-value.js'; +import { ChatService } from '@/core/ChatService.js'; import Channel, { type MiChannelService } from '../channel.js'; class ChatChannel extends Channel { @@ -17,6 +18,8 @@ class ChatChannel extends Channel { private otherId: string; constructor( + private chatService: ChatService, + id: string, connection: Channel['connection'], ) { @@ -36,6 +39,17 @@ class ChatChannel extends Channel { this.send(data.type, data.body); } + @bindThis + public onMessage(type: string, body: any) { + switch (type) { + case 'read': + if (this.otherId) { + this.chatService.readUserChatMessage(this.user!.id, this.otherId); + } + break; + } + } + @bindThis public dispose() { // Unsubscribe events @@ -50,12 +64,14 @@ export class ChatChannelService implements MiChannelService { public readonly kind = ChatChannel.kind; constructor( + private chatService: ChatService, ) { } @bindThis public create(id: string, connection: Channel['connection']): ChatChannel { return new ChatChannel( + this.chatService, id, connection, ); diff --git a/packages/frontend-shared/js/scroll.ts b/packages/frontend-shared/js/scroll.ts index 508864b12c..6c61c582e1 100644 --- a/packages/frontend-shared/js/scroll.ts +++ b/packages/frontend-shared/js/scroll.ts @@ -38,7 +38,7 @@ export function getScrollPosition(el: HTMLElement | null): number { export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknown, tolerance = 1, once = false) { // とりあえず評価してみる - const firstTopVisible = isTopVisible(el); + const firstTopVisible = isHeadVisible(el); if (el.isConnected && firstTopVisible) { cb(firstTopVisible); if (once) return null; @@ -53,7 +53,7 @@ export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknow const onScroll = () => { if (!document.body.contains(el)) return; - const topVisible = isTopVisible(el, tolerance); + const topVisible = isHeadVisible(el, tolerance); if (topVisible !== prevTopVisible) { prevTopVisible = topVisible; cb(topVisible); @@ -71,7 +71,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1 const container = getScrollContainer(el); // とりあえず評価してみる - if (el.isConnected && isBottomVisible(el, tolerance, container)) { + if (el.isConnected && isTailVisible(el, tolerance, container)) { cb(); if (once) return null; } @@ -79,7 +79,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1 const containerOrWindow = container ?? window; const onScroll = () => { if (!document.body.contains(el)) return; - if (isBottomVisible(el, 1, container)) { + if (isTailVisible(el, 1, container)) { cb(); if (once) removeListener(); } @@ -132,12 +132,12 @@ export function scrollToBottom( } } -export function isTopVisible(el: HTMLElement, tolerance = 1): boolean { +export function isHeadVisible(el: HTMLElement, tolerance = 1): boolean { const scrollTop = getScrollPosition(el); return scrollTop <= tolerance; } -export function isBottomVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) { +export function isTailVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) { if (container) return el.scrollHeight <= container.clientHeight + Math.abs(container.scrollTop) + tolerance; return el.scrollHeight <= window.innerHeight + window.scrollY + tolerance; } diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index b5842876ac..ec6fcdc311 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -168,21 +168,17 @@ export default defineComponent({ container-type: inline-size; &:global { - > .list-move { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } + > .list-move { + transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); + } - &.deny-move-transition > .list-move { - transition: none !important; - } + > .list-enter-active { + transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); + } - > .list-enter-active { - transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); - } - - > *:empty { - display: none; - } + > *:empty { + display: none; + } } &:not(.date-separated-list-nogap) > *:not(:last-child) { diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index ab8bda403b..a2a0ca40c0 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { useDocumentVisibility } from '@@/js/use-document-visibility.js'; -import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js'; +import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js'; import type { ComputedRef } from 'vue'; import type { MisskeyEntity } from '@/types/date-separated-list.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -97,6 +97,7 @@ const props = withDefaults(defineProps<{ pagination: Paging; disableAutoLoad?: boolean; displayLimit?: number; + scrollReversed?: boolean; }>(), { displayLimit: 20, }); @@ -349,7 +350,7 @@ const appearFetchMoreAhead = async (): Promise => { fetchMoreAppearTimeout(); }; -const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE); +const isHead = (): boolean => isBackTop.value || (props.pagination.reversed && !props.scrollReversed ? isTailVisible : isHeadVisible)(contentEl.value!, TOLERANCE); watch(visibility, () => { if (visibility.value === 'hidden') { @@ -364,7 +365,7 @@ watch(visibility, () => { timerForSetPause = null; } else { isPausingUpdate = false; - if (isTop()) { + if (isHead()) { executeQueue(); } } @@ -376,16 +377,18 @@ watch(visibility, () => { * ストリーミングから降ってきたアイテムはこれで追加する * @param item アイテム */ -const prepend = (item: MisskeyEntity): void => { +function prepend(item: MisskeyEntity): void { if (items.value.size === 0) { items.value.set(item.id, item); fetching.value = false; return; } - if (isTop() && !isPausingUpdate) unshiftItems([item]); + console.log(isHead(), isPausingUpdate); + + if (isHead() && !isPausingUpdate) unshiftItems([item]); else prependQueue(item); -}; +} /** * 新着アイテムをitemsの先頭に追加し、displayLimitを適用する diff --git a/packages/frontend/src/pages/chat/home.vue b/packages/frontend/src/pages/chat/home.vue index ab7b549956..ffb9511a88 100644 --- a/packages/frontend/src/pages/chat/home.vue +++ b/packages/frontend/src/pages/chat/home.vue @@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only -->