chat fixes

This commit is contained in:
Henry Jameson 2022-04-10 19:28:26 +03:00
parent 7426417a52
commit 5b664f464d
4 changed files with 21 additions and 74 deletions

View file

@ -19,8 +19,6 @@ library.add(
faChevronLeft faChevronLeft
) )
const scroller = () => document.getElementById('content')
const BOTTOMED_OUT_OFFSET = 10 const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10 const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
const SAFE_RESIZE_TIME_OFFSET = 100 const SAFE_RESIZE_TIME_OFFSET = 100
@ -45,10 +43,9 @@ const Chat = {
}, },
created () { created () {
this.startFetching() this.startFetching()
window.addEventListener('resize', this.handleLayoutChange)
}, },
mounted () { mounted () {
scroller().addEventListener('scroll', this.handleScroll) window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') { if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false) document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
} }
@ -56,12 +53,9 @@ const Chat = {
this.$nextTick(() => { this.$nextTick(() => {
this.handleResize() this.handleResize()
}) })
this.setChatLayout()
}, },
unmounted () { unmounted () {
scroller().removeEventListener('scroll', this.handleScroll) window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout()
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false) if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.dispatch('clearCurrentChat') this.$store.dispatch('clearCurrentChat')
}, },
@ -97,8 +91,6 @@ const Chat = {
...mapState({ ...mapState({
backendInteractor: state => state.api.backendInteractor, backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus, mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
mobileLayout: state => state.interface.mobileLayout,
layoutHeight: state => state.interface.layoutHeight,
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser
}) })
}, },
@ -116,9 +108,6 @@ const Chat = {
'$route': function () { '$route': function () {
this.startFetching() this.startFetching()
}, },
layoutHeight () {
this.handleResize({ expand: true })
},
mastoUserSocketStatus (newValue) { mastoUserSocketStatus (newValue) {
if (newValue === WSConnectionStatus.JOINED) { if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true }) this.fetchChat({ isFirstFetch: true })
@ -142,30 +131,6 @@ const Chat = {
} }
}) })
}, },
setChatLayout () {
// This is a hacky way to adjust the global layout to the mobile chat (without modifying the rest of the app).
// This layout prevents empty spaces from being visible at the bottom
// of the chat on iOS Safari (`safe-area-inset`) when
// - the on-screen keyboard appears and the user starts typing
// - the user selects the text inside the input area
// - the user selects and deletes the text that is multiple lines long
// TODO: unify the chat layout with the global layout.
let html = document.querySelector('html')
if (html) {
html.classList.add('chat-layout')
}
},
unsetChatLayout () {
let html = document.querySelector('html')
if (html) {
html.classList.remove('chat-layout')
}
},
handleLayoutChange () {
this.$nextTick(() => {
this.scrollDown()
})
},
// Preserves the scroll position when OSK appears or the posting form changes its height. // Preserves the scroll position when OSK appears or the posting form changes its height.
handleResize (opts = {}) { handleResize (opts = {}) {
const { expand = false, delayed = false } = opts const { expand = false, delayed = false } = opts
@ -179,25 +144,20 @@ const Chat = {
this.$nextTick(() => { this.$nextTick(() => {
const { offsetHeight = undefined } = this.lastScrollPosition const { offsetHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition(scroller()) this.lastScrollPosition = getScrollPosition()
const diff = this.lastScrollPosition.offsetHeight - offsetHeight const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff < 0 || (!this.bottomedOut() && expand)) { if (diff < 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => { this.$nextTick(() => {
scroller().scrollTo({ window.scrollTo({ top: window.scrollY - diff })
top: scroller().scrollTop - diff,
left: 0
})
}) })
} }
}) })
}, },
scrollDown (options = {}) { scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options const { behavior = 'auto', forceRead = false } = options
const scrollable = scroller()
if (!scrollable) { return }
this.$nextTick(() => { this.$nextTick(() => {
scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior }) window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
}) })
if (forceRead) { if (forceRead) {
this.readChat() this.readChat()
@ -213,11 +173,10 @@ const Chat = {
}) })
}, },
bottomedOut (offset) { bottomedOut (offset) {
return isBottomedOut(scroller(), offset) return isBottomedOut(offset)
}, },
reachedTop () { reachedTop () {
const scrollable = scroller() return window.scrollY <= 0
return scrollable && scrollable.scrollTop <= 0
}, },
cullOlderCheck () { cullOlderCheck () {
window.setTimeout(() => { window.setTimeout(() => {
@ -248,10 +207,9 @@ const Chat = {
} }
}, 200), }, 200),
handleScrollUp (positionBeforeLoading) { handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition(scroller()) const positionAfterLoading = getScrollPosition()
scroller().scrollTo({ window.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading), top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
left: 0
}) })
}, },
fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) { fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
@ -270,7 +228,7 @@ const Chat = {
chatService.clear(chatMessageService) chatService.clear(chatMessageService)
} }
const positionBeforeUpdate = getScrollPosition(scroller()) const positionBeforeUpdate = getScrollPosition()
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => { this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => { this.$nextTick(() => {
if (fetchOlderMessages) { if (fetchOlderMessages) {
@ -281,7 +239,7 @@ const Chat = {
// full height of the scrollable container. // full height of the scrollable container.
// If this is the case, we want to fetch the messages until the scrollable container // If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history. // is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable(scroller()) && messages.length > 0) { if (!isScrollable() && messages.length > 0) {
this.fetchChat({ maxId: this.currentChatMessageService.minId }) this.fetchChat({ maxId: this.currentChatMessageService.minId })
} }
}) })

View file

@ -2,7 +2,6 @@
<div class="chat-view"> <div class="chat-view">
<div class="chat-view-inner"> <div class="chat-view-inner">
<div <div
id="nav"
ref="inner" ref="inner"
class="panel-default panel chat-view-body" class="panel-default panel chat-view-body"
> >

View file

@ -1,9 +1,9 @@
// Captures a scroll position // Captures a scroll position
export const getScrollPosition = (el) => { export const getScrollPosition = () => {
return { return {
scrollTop: el.scrollTop, scrollTop: window.scrollY,
scrollHeight: el.scrollHeight, scrollHeight: document.documentElement.scrollHeight,
offsetHeight: el.offsetHeight offsetHeight: window.innerHeight
} }
} }
@ -13,21 +13,12 @@ export const getNewTopPosition = (previousPosition, newPosition) => {
return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight) return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
} }
export const isBottomedOut = (el, offset = 0) => { export const isBottomedOut = (offset = 0) => {
if (!el) { return } const scrollHeight = window.scrollY + offset
const scrollHeight = el.scrollTop + offset const totalHeight = document.documentElement.scrollHeight - window.innerHeight
const totalHeight = el.scrollHeight - el.offsetHeight
return totalHeight <= scrollHeight return totalHeight <= scrollHeight
} }
// Height of the scrollable container. The dynamic height is needed to ensure the mobile browser panel doesn't overlap or hide the posting form.
export const scrollableContainerHeight = (inner, header, footer) => {
return inner.offsetHeight - header.clientHeight - footer.clientHeight
}
// Returns whether or not the scrollbar is visible. // Returns whether or not the scrollbar is visible.
export const isScrollable = (el) => { export const isScrollable = () => {
if (!el) return return document.documentElement.scrollHeight > window.innerHeight
return el.scrollHeight > el.clientHeight
} }

View file

@ -1,6 +1,5 @@
<template> <template>
<div <div
id="nav"
class="panel-default panel chat-new" class="panel-default panel chat-new"
> >
<div <div