Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e0f2c04
refactor: useMessageScroller を作成し、メッセージのスクロールに関する処理を統合
yas-ako Dec 31, 2025
637a759
Revert "fix: 初回の高さの変化に対しても `change-height` イベントを emit する"
uni-kakurenbo Jan 2, 2026
265f949
Revert "fix: より古いメッセージを新たに読み込んだとき、スクロール位置が下にずれてしまう問題を修正"
uni-kakurenbo Jan 2, 2026
30099a5
refactor: 初回レンダリングの判定をより明示的にする
uni-kakurenbo Jan 2, 2026
431271a
refactor: 命名とシグネチャの改善
uni-kakurenbo Jan 2, 2026
406ee00
refactor: コードブロックが存在する場合にも高さを監視する
uni-kakurenbo Jan 2, 2026
7efb494
fix: `ResizeObserver` の登録を待ってから描画する
uni-kakurenbo Jan 2, 2026
783c1b9
tests: `timer.ts` のテストを追加
uni-kakurenbo Jan 2, 2026
d2a9f35
fix: 初回ロード時にスクロール位置が末尾から少し上にズレる不具合を修正
uni-kakurenbo Jan 3, 2026
48a8056
feat: 初回ロード時にスケルトンを表示する
uni-kakurenbo Jan 3, 2026
d0d3814
feat: スケルトンをリッチにする
uni-kakurenbo Jan 3, 2026
b6904d0
feat: スクロール速度に応じて閾値を調整する
uni-kakurenbo Jan 3, 2026
ef27ab4
feat: オーバースクロール時にもスケルトンを表示する
uni-kakurenbo Jan 3, 2026
8b7c178
fix: ref の fallthrough 先が変わっていたのを修正
uni-kakurenbo Jan 3, 2026
342f836
modify: iOS とモバイルでスクロールの先読みを無効化
uni-kakurenbo Jan 3, 2026
d8c9902
fix: 先読みが無効化されるときの挙動を改善
uni-kakurenbo Jan 4, 2026
79af203
fix: lint, format
uni-kakurenbo Mar 23, 2026
90668ee
chore: overscroll を無効に
yas-ako Mar 23, 2026
54c4ec0
fix: 必要ない隙間を削除
yas-ako Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const toNewMessage = () => {
}
}

const element = unrefElement(scrollerRef)
const element = unrefElement(scrollerRef.value?.rootRef)
if (!element) return

element.scrollTo({
Expand All @@ -146,7 +146,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
Expand Down
5 changes: 3 additions & 2 deletions src/components/Main/MainView/MessageElement/ClipElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 19 additions & 16 deletions src/components/Main/MainView/MessageElement/MessageContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,32 @@
:channel-id="message.channelId"
@finish-editing="finishEditing"
/>
<MessageQuoteList
v-if="embeddingsState.quoteMessageIds.length > 0"
:class="$style.messageEmbeddingsList"
:parent-message-channel-id="message.channelId"
:message-ids="embeddingsState.quoteMessageIds"
/>
<MessageFileList
v-if="embeddingsState.fileIds.length > 0"
:class="$style.messageEmbeddingsList"
:channel-id="message.channelId"
:file-ids="embeddingsState.fileIds"
/>
<MessageOgpList
v-if="embeddingsState.externalUrls.length > 0"
:external-urls="embeddingsState.externalUrls"
/>
<DeferredRender>
<MessageQuoteList
v-if="embeddingsState.quoteMessageIds.length > 0"
:class="$style.messageEmbeddingsList"
:parent-message-channel-id="message.channelId"
:message-ids="embeddingsState.quoteMessageIds"
/>
<MessageFileList
v-if="embeddingsState.fileIds.length > 0"
:class="$style.messageEmbeddingsList"
:channel-id="message.channelId"
:file-ids="embeddingsState.fileIds"
/>
<MessageOgpList
v-if="embeddingsState.externalUrls.length > 0"
:external-urls="embeddingsState.externalUrls"
/>
</DeferredRender>
</div>
</div>
</template>

<script lang="ts" setup>
import { computed } from 'vue'

import DeferredRender from '/@/components/UI/DeferredRender.vue'
import MarkdownContent from '/@/components/UI/MarkdownContent.vue'
import UserIcon from '/@/components/UI/UserIcon.vue'
import useEmbeddings from '/@/composables/message/useEmbeddings'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ import type { MessageId, UserId } from '/@/types/entity-ids'
import MessageContents from './MessageContents.vue'
import MessagePinned from './MessagePinned.vue'
import MessageStampList from './MessageStampList.vue'
import type { ChangeHeightData } from './composables/useElementRenderObserver'
import useElementRenderObserver from './composables/useElementRenderObserver'
import useElementRenderObserver, {
type ChangeHeightData
} from './composables/useElementRenderObserver'

const props = withDefaults(
defineProps<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,27 @@ const useElementRenderObserver = (
quoteMessageIds: readonly MessageId[]
fileIds: readonly FileId[]
externalUrls: readonly ExternalUrl[]
hasCodeBlock: boolean
}>,
emit: ((name: 'entryMessageLoaded', relativePos: number) => void) &
((name: 'changeHeight', data: ChangeHeightData) => void)
) => {
const route = useRoute()

let lastHeight = 0
let lastBottom = 0
let lastTop = 0
type State = {
height: number
bottom: number
top: number
}

let prevState: State | null = null

const resizeObserver = new ResizeObserver(entries => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const entry = entries[0]!
const { height, bottom, top } = entry.target.getBoundingClientRect()

emit('changeHeight', {
id: messageId.value,
heightDiff: height - lastHeight,
top,
bottom,
lastTop,
lastBottom
})

if (lastHeight === 0) {
if (prevState === null) {
// 初回に高さが変化した場合、初期レンダリング完了とみなす
// これ以降新規にobserveしないためにwatcherを止める
stop()
Expand All @@ -56,23 +53,32 @@ const useElementRenderObserver = (
const { top } = bodyRef.value.getBoundingClientRect()
emit('entryMessageLoaded', top - parentTop)
}
} else {
emit('changeHeight', {
id: messageId.value,
heightDiff: height - prevState.height,
top,
bottom,
lastTop: prevState.top,
lastBottom: prevState.bottom
})
}

lastHeight = height
lastBottom = bottom
lastTop = top
prevState = { height, bottom, top }
})

const stop = watchEffect(
() => {
if (
(unref(isEntryMessage) ||
embeddingsState.quoteMessageIds.length > 0 ||
embeddingsState.fileIds.length > 0 ||
embeddingsState.externalUrls.length > 0) &&
embeddingsState.externalUrls.length > 0 ||
embeddingsState.hasCodeBlock) &&
bodyRef.value
) {
/*
引用 / 添付ファイル / 外部URL がある場合か
引用 / 添付ファイル / 外部URL / コードブロック がある場合か
エントリーメッセージは
高さ監視をする
*/
Expand All @@ -82,6 +88,7 @@ const useElementRenderObserver = (
// 監視前に高さが変わってしまうのを防止するためにsyncを指定する
{ flush: 'sync' }
)

watch(
() => route.path,
() =>
Expand Down
Loading
Loading