diff --git a/src/components/Main/MainView/ChannelView/ChannelViewContent/ChannelViewContentMain.vue b/src/components/Main/MainView/ChannelView/ChannelViewContent/ChannelViewContentMain.vue
index 87d3508a33..f670d7ac5c 100644
--- a/src/components/Main/MainView/ChannelView/ChannelViewContent/ChannelViewContentMain.vue
+++ b/src/components/Main/MainView/ChannelView/ChannelViewContent/ChannelViewContentMain.vue
@@ -7,6 +7,7 @@
:is-reached-end="isReachedEnd"
:is-reached-latest="isReachedLatest"
:is-loading="isLoading"
+ :is-initial-load="isInitialLoad"
:entry-message-id="entryMessageId"
:last-loading-direction="lastLoadingDirection"
@request-load-former="onLoadFormerMessagesRequest"
@@ -29,6 +30,7 @@
:class="$style.element"
:message-id="messageId"
:is-archived="isArchived"
+ :is-initial-load="isInitialLoad"
:is-entry-message="messageId === entryMessageId"
:pinned-user-id="messagePinnedUserMap.get(messageId)"
@change-height="onChangeHeight"
@@ -85,6 +87,7 @@ const {
isReachedEnd,
isReachedLatest,
isLoading,
+ isInitialLoad,
lastLoadingDirection,
unreadSince,
onLoadFormerMessagesRequest,
@@ -137,7 +140,7 @@ const toNewMessage = () => {
}
}
- const element = unrefElement(scrollerRef)
+ const element = unrefElement(scrollerRef.value?.rootRef)
if (!element) return
element.scrollTo({
@@ -146,7 +149,7 @@ const toNewMessage = () => {
}
const handleScroll = () => {
- const element = unrefElement(scrollerRef)
+ const element = unrefElement(scrollerRef.value?.rootRef)
if (!element || isLoading.value) return
const { scrollTop, scrollHeight, clientHeight } = element
showToNewMessageButton.value = scrollHeight - 2 * clientHeight > scrollTop
diff --git a/src/components/Main/MainView/MessageElement/ClipElement.vue b/src/components/Main/MainView/MessageElement/ClipElement.vue
index 328825d9e9..6c3df45523 100644
--- a/src/components/Main/MainView/MessageElement/ClipElement.vue
+++ b/src/components/Main/MainView/MessageElement/ClipElement.vue
@@ -40,8 +40,9 @@ import type { MessageId } from '/@/types/entity-ids'
import MessageQuoteListItemFooter from './Embeddings/MessageQuoteListItemFooter.vue'
import MessageContents from './MessageContents.vue'
import MessageTools, { useMessageToolsHover } from './MessageTools.vue'
-import type { ChangeHeightData } from './composables/useElementRenderObserver'
-import useElementRenderObserver from './composables/useElementRenderObserver'
+import useElementRenderObserver, {
+ type ChangeHeightData
+} from './composables/useElementRenderObserver'
const props = defineProps<{
messageId: MessageId
diff --git a/src/components/Main/MainView/MessageElement/MessageContents.vue b/src/components/Main/MainView/MessageElement/MessageContents.vue
index 1f3bf65b3b..e556d34fe7 100644
--- a/src/components/Main/MainView/MessageElement/MessageContents.vue
+++ b/src/components/Main/MainView/MessageElement/MessageContents.vue
@@ -8,7 +8,11 @@
:updated-at="message.updatedAt"
/>
-
+
-
-
-
+
+
+
+
+
@@ -39,6 +45,7 @@
diff --git a/src/components/Main/MainView/MessagesScroller/MessagesSkeleton.vue b/src/components/Main/MainView/MessagesScroller/MessagesSkeleton.vue
new file mode 100644
index 0000000000..4aed495ae0
--- /dev/null
+++ b/src/components/Main/MainView/MessagesScroller/MessagesSkeleton.vue
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
diff --git a/src/components/Main/MainView/MessagesScroller/composables/useMessageScroller.ts b/src/components/Main/MainView/MessagesScroller/composables/useMessageScroller.ts
new file mode 100644
index 0000000000..87a9520b8f
--- /dev/null
+++ b/src/components/Main/MainView/MessagesScroller/composables/useMessageScroller.ts
@@ -0,0 +1,152 @@
+import type { MaybeRefOrGetter, Reactive, Ref } from 'vue'
+import { reactive, toValue, watch } from 'vue'
+import { ref } from 'vue'
+
+import type { ChangeHeightData } from '/@/components/Main/MainView/MessageElement/composables/useElementRenderObserver'
+import type { LoadingDirection } from '/@/components/Main/MainView/MessagesScroller/composables/useMessagesFetcher'
+import { defer } from '/@/lib/basic/timer'
+import type { MessageId } from '/@/types/entity-ids'
+
+const useMessageScroller = (
+ rootRef: Ref,
+ scrollerProps: Reactive<{
+ messageIds: MessageId[]
+ lastLoadingDirection: LoadingDirection
+ entryMessageId?: MessageId
+ }>,
+ topSkeletonHeight: MaybeRefOrGetter = 0
+) => {
+ const ready = ref(false)
+
+ const state = reactive({
+ height: 0,
+ scrollTop: 0
+ })
+
+ const onChangeHeight = async (payload: ChangeHeightData) => {
+ if (!rootRef.value) return
+
+ if (
+ scrollerProps.lastLoadingDirection === 'around' &&
+ scrollerProps.entryMessageId
+ ) {
+ // エントリーメッセージがあり、かつ初回ロードの場合
+ // エントリーより上にあった場合はスクロール位置をずらす
+ let isBeforeEntryMessage = false
+ for (const messageId of scrollerProps.messageIds) {
+ if (messageId === scrollerProps.entryMessageId) break
+ if (messageId === payload.id) {
+ isBeforeEntryMessage = true
+ break
+ }
+ }
+
+ if (isBeforeEntryMessage) {
+ rootRef.value.scrollTo({
+ top: rootRef.value.scrollTop + payload.heightDiff
+ })
+ state.height = rootRef.value.scrollHeight
+ }
+ } else if (
+ scrollerProps.lastLoadingDirection === 'latest' ||
+ scrollerProps.lastLoadingDirection === 'former'
+ ) {
+ const scrollerTop =
+ rootRef.value.getBoundingClientRect().top + toValue(topSkeletonHeight)
+
+ // 視界より上にある要素の高さが変わった場合のみ補正する
+ if (payload.top < scrollerTop) {
+ rootRef.value.scrollTo({
+ top: rootRef.value.scrollTop + payload.heightDiff
+ })
+ }
+
+ const scrollBottom = rootRef.value.scrollTop + rootRef.value.clientHeight
+
+ // 末尾付近を閲覧しているときは,末尾を保つ
+ if (state.height - scrollBottom <= 50) {
+ if (!rootRef.value) return
+
+ // 複数の onChangeHeight が同フレーム内に複数回呼ばれる場合があるので,
+ // すべての更新を待ってからスクロールする
+ await defer()
+ rootRef.value.scrollTo({ top: rootRef.value.scrollHeight })
+ }
+
+ state.height = rootRef.value.scrollHeight
+ }
+ }
+
+ const onEntryMessageLoaded = (relativePos: number) => {
+ if (!rootRef.value) return
+
+ const rootHeight = rootRef.value.getBoundingClientRect().height
+ rootRef.value.scrollTo({ top: relativePos - rootHeight / 3 })
+ }
+
+ watch(
+ () => scrollerProps.messageIds,
+ (ids, prevIds) => {
+ if (!rootRef.value) return
+ /* state.height の更新を忘れないようにすること */
+
+ const newHeight = rootRef.value.scrollHeight
+ if (
+ scrollerProps.lastLoadingDirection === 'latest' ||
+ scrollerProps.lastLoadingDirection === 'former'
+ ) {
+ if (ids.length - prevIds.length === -1) {
+ // 削除された場合は何もしない
+ state.height = newHeight
+ return
+ }
+ // XXX: 追加時にここは0になる
+ if (ids.length - prevIds.length === 0) {
+ const scrollBottom =
+ rootRef.value.scrollTop + rootRef.value.clientHeight
+
+ // 一番下のメッセージあたりを見ているときに、
+ // 新規に一つ追加された場合は一番下までスクロール
+ if (state.height - 50 <= scrollBottom) {
+ rootRef.value.scrollTo({ top: newHeight })
+ }
+ state.height = newHeight
+
+ return
+ }
+ //上に追加された時はスクロール位置を変更する。
+ if (scrollerProps.lastLoadingDirection === 'former') {
+ rootRef.value.scrollTo({
+ top: rootRef.value.scrollTop + newHeight - state.height
+ })
+ state.height = newHeight
+ }
+
+ if (scrollerProps.lastLoadingDirection === 'latest') {
+ ready.value = false
+
+ // チャンネルを移動したとき、
+ rootRef.value.scrollTo({
+ top: newHeight
+ })
+
+ state.height = newHeight
+
+ defer(() => {
+ ready.value = true
+ })
+ }
+ } else state.height = newHeight
+ },
+ { deep: true, flush: 'post' }
+ )
+
+ return {
+ onChangeHeight,
+ onEntryMessageLoaded,
+ ready,
+ state
+ }
+}
+
+export default useMessageScroller
diff --git a/src/components/Main/MainView/MessagesScroller/composables/useMessageScrollerElementResizeObserver.ts b/src/components/Main/MainView/MessagesScroller/composables/useMessageScrollerElementResizeObserver.ts
deleted file mode 100644
index a5d5a1effa..0000000000
--- a/src/components/Main/MainView/MessagesScroller/composables/useMessageScrollerElementResizeObserver.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import type { Ref } from 'vue'
-
-import type { ChangeHeightData } from '/@/components/Main/MainView/MessageElement/composables/useElementRenderObserver'
-import type { MessageId } from '/@/types/entity-ids'
-
-import type { LoadingDirection } from './useMessagesFetcher'
-
-const useMessageScrollerElementResizeObserver = (
- rootRef: Ref,
- scrollerProps: {
- messageIds: MessageId[]
- lastLoadingDirection: LoadingDirection
- entryMessageId?: MessageId
- },
- viewPortState: {
- height: number
- skipResizeAdjustment?: boolean
- }
-) => {
- const onChangeHeight = (payload: ChangeHeightData) => {
- if (!rootRef.value) {
- return
- }
-
- if (
- scrollerProps.lastLoadingDirection === 'around' &&
- scrollerProps.entryMessageId
- ) {
- // エントリーメッセージがあり、かつ初回ロードの場合
- // エントリーより上にあった場合はスクロール位置をずらす
- let isBeforeEntryMessage = false
- for (const messageId of scrollerProps.messageIds) {
- if (messageId === scrollerProps.entryMessageId) break
- if (messageId === payload.id) {
- isBeforeEntryMessage = true
- break
- }
- }
-
- if (isBeforeEntryMessage) {
- rootRef.value.scrollTop += payload.heightDiff
- viewPortState.height = rootRef.value.scrollHeight
- }
- } else if (
- scrollerProps.lastLoadingDirection === 'latest' ||
- scrollerProps.lastLoadingDirection === 'former'
- ) {
- // 古いメッセージを読み込んですぐはここで高さの調整をしない
- if (viewPortState.skipResizeAdjustment) {
- return
- }
- rootRef.value.scrollTop += payload.heightDiff
- viewPortState.height = rootRef.value.scrollHeight
- }
- }
-
- const onEntryMessageLoaded = (relativePos: number) => {
- if (!rootRef.value) {
- return
- }
- const rootHeight = rootRef.value.getBoundingClientRect().height
- rootRef.value.scrollTop = relativePos - rootHeight / 3
- }
-
- return { onChangeHeight, onEntryMessageLoaded }
-}
-
-export default useMessageScrollerElementResizeObserver
diff --git a/src/components/Main/MainView/MessagesScroller/composables/useMessagesFetcher.ts b/src/components/Main/MainView/MessagesScroller/composables/useMessagesFetcher.ts
index de7079ff7f..1ad11a5759 100644
--- a/src/components/Main/MainView/MessagesScroller/composables/useMessagesFetcher.ts
+++ b/src/components/Main/MainView/MessagesScroller/composables/useMessagesFetcher.ts
@@ -213,6 +213,7 @@ const useMessageFetcher = (
const init = () => {
resetRenderedContent()
+ isInitialLoad.value = true
if (props.entryMessageId) {
onLoadAroundMessagesRequest(props.entryMessageId)
} else {
diff --git a/src/components/UI/DeferredRender.vue b/src/components/UI/DeferredRender.vue
new file mode 100644
index 0000000000..2818b50ea0
--- /dev/null
+++ b/src/components/UI/DeferredRender.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/components/UI/MarkdownContent.vue b/src/components/UI/MarkdownContent.vue
index 2d125db7d4..e279177154 100644
--- a/src/components/UI/MarkdownContent.vue
+++ b/src/components/UI/MarkdownContent.vue
@@ -10,17 +10,25 @@