forked from Mirrors/gomuks
Compare commits
1 commit
main
...
tulir/colu
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d08cbb4433 |
6 changed files with 28 additions and 45 deletions
|
@ -420,11 +420,7 @@ const MessageComposer = () => {
|
||||||
}
|
}
|
||||||
textInput.current.rows = newTextRows
|
textInput.current.rows = newTextRows
|
||||||
textRows.current = newTextRows
|
textRows.current = newTextRows
|
||||||
// This has to be called unconditionally, because setting rows = 1 messes up the scroll state otherwise
|
}, [state.text])
|
||||||
roomCtx.scrollToBottom()
|
|
||||||
// scrollToBottom needs to be called when replies/attachments/etc change,
|
|
||||||
// so listen to state instead of only state.text
|
|
||||||
}, [state, roomCtx])
|
|
||||||
// Saving to localStorage could be done in the reducer, but that's not very proper, so do it in an effect.
|
// Saving to localStorage could be done in the reducer, but that's not very proper, so do it in an effect.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
roomCtx.isEditing.emit(editing !== null)
|
roomCtx.isEditing.emit(editing !== null)
|
||||||
|
|
|
@ -33,9 +33,7 @@ const RoomView = ({ room, rightPanelResizeHandle, rightPanel }: RoomViewProps) =
|
||||||
const [roomContextData] = useState(() => new RoomContextData(room))
|
const [roomContextData] = useState(() => new RoomContextData(room))
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.activeRoomContext = roomContextData
|
window.activeRoomContext = roomContextData
|
||||||
window.addEventListener("resize", roomContextData.scrollToBottom)
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", roomContextData.scrollToBottom)
|
|
||||||
if (window.activeRoomContext === roomContextData) {
|
if (window.activeRoomContext === roomContextData) {
|
||||||
window.activeRoomContext = undefined
|
window.activeRoomContext = undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import { RefObject, createContext, createRef, use } from "react"
|
import { createContext, use } from "react"
|
||||||
import { RoomStateStore } from "@/api/statestore"
|
import { RoomStateStore } from "@/api/statestore"
|
||||||
import { EventID, EventRowID, MemDBEvent } from "@/api/types"
|
import { EventID, EventRowID, MemDBEvent } from "@/api/types"
|
||||||
import { NonNullCachedEventDispatcher } from "@/util/eventdispatcher.ts"
|
import { NonNullCachedEventDispatcher } from "@/util/eventdispatcher.ts"
|
||||||
|
@ -24,7 +24,6 @@ const noop = (name: string) => () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RoomContextData {
|
export class RoomContextData {
|
||||||
public readonly timelineBottomRef: RefObject<HTMLDivElement | null> = createRef()
|
|
||||||
public setReplyTo: (eventID: EventID | null) => void = noop("setReplyTo")
|
public setReplyTo: (eventID: EventID | null) => void = noop("setReplyTo")
|
||||||
public setEditing: (evt: MemDBEvent | null) => void = noop("setEditing")
|
public setEditing: (evt: MemDBEvent | null) => void = noop("setEditing")
|
||||||
public insertText: (text: string) => void = noop("insertText")
|
public insertText: (text: string) => void = noop("insertText")
|
||||||
|
@ -35,12 +34,6 @@ export class RoomContextData {
|
||||||
|
|
||||||
constructor(public store: RoomStateStore) {}
|
constructor(public store: RoomStateStore) {}
|
||||||
|
|
||||||
scrollToBottom = () => {
|
|
||||||
if (this.scrolledToBottom) {
|
|
||||||
this.timelineBottomRef.current?.scrollIntoView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFocusedEventRowID = (eventRowID: number | null) => {
|
setFocusedEventRowID = (eventRowID: number | null) => {
|
||||||
this.directSetFocusedEventRowID(eventRowID)
|
this.directSetFocusedEventRowID(eventRowID)
|
||||||
this.focusedEventRowID = eventRowID
|
this.focusedEventRowID = eventRowID
|
||||||
|
|
|
@ -11,6 +11,8 @@ div.timeline-event {
|
||||||
/ var(--timeline-avatar-size) var(--timeline-avatar-gap) 1fr var(--timeline-status-size);
|
/ var(--timeline-avatar-size) var(--timeline-avatar-gap) 1fr var(--timeline-status-size);
|
||||||
contain: layout;
|
contain: layout;
|
||||||
margin-top: var(--timeline-message-gap);
|
margin-top: var(--timeline-message-gap);
|
||||||
|
content-visibility: auto;
|
||||||
|
contain-intrinsic-height: auto 3rem;
|
||||||
|
|
||||||
&.highlight {
|
&.highlight {
|
||||||
background-color: var(--timeline-highlight-bg-color);
|
background-color: var(--timeline-highlight-bg-color);
|
||||||
|
@ -25,6 +27,7 @@ div.timeline-event {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover:not(.no-hover), &.focused-event {
|
&:hover:not(.no-hover), &.focused-event {
|
||||||
|
content-visibility: visible;
|
||||||
background-color: var(--timeline-hover-bg-color);
|
background-color: var(--timeline-hover-bg-color);
|
||||||
|
|
||||||
&.highlight {
|
&.highlight {
|
||||||
|
|
|
@ -2,7 +2,7 @@ div.timeline-view {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column-reverse;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
> div.timeline-beginning {
|
> div.timeline-beginning {
|
||||||
|
@ -19,5 +19,7 @@ div.timeline-view {
|
||||||
|
|
||||||
> div.timeline-list {
|
> div.timeline-list {
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,10 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import { use, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"
|
import { use, useCallback, useEffect, useRef, useState } from "react"
|
||||||
import { ScaleLoader } from "react-spinners"
|
import { ScaleLoader } from "react-spinners"
|
||||||
import { usePreference, useRoomTimeline } from "@/api/statestore"
|
import { usePreference, useRoomTimeline } from "@/api/statestore"
|
||||||
import { EventRowID, MemDBEvent } from "@/api/types"
|
import { EventRowID, MemDBEvent, TimelineRowID } from "@/api/types"
|
||||||
import useFocus from "@/util/focus.ts"
|
import useFocus from "@/util/focus.ts"
|
||||||
import ClientContext from "../ClientContext.ts"
|
import ClientContext from "../ClientContext.ts"
|
||||||
import { useRoomContext } from "../roomview/roomcontext.ts"
|
import { useRoomContext } from "../roomview/roomcontext.ts"
|
||||||
|
@ -36,11 +36,9 @@ const TimelineView = () => {
|
||||||
.catch(err => console.error("Failed to load history", err))
|
.catch(err => console.error("Failed to load history", err))
|
||||||
.finally(() => setLoadingHistory(false))
|
.finally(() => setLoadingHistory(false))
|
||||||
}, [client, room])
|
}, [client, room])
|
||||||
const bottomRef = roomCtx.timelineBottomRef
|
|
||||||
const topRef = useRef<HTMLDivElement>(null)
|
const topRef = useRef<HTMLDivElement>(null)
|
||||||
const timelineViewRef = useRef<HTMLDivElement>(null)
|
const timelineViewRef = useRef<HTMLDivElement>(null)
|
||||||
const prevOldestTimelineRow = useRef(0)
|
const oldestTimelineRow = useRef<TimelineRowID>(timeline[0]?.timeline_rowid ?? 0)
|
||||||
const oldestTimelineRow = timeline[0]?.timeline_rowid
|
|
||||||
const oldScrollHeight = useRef(0)
|
const oldScrollHeight = useRef(0)
|
||||||
const focused = useFocus()
|
const focused = useFocus()
|
||||||
const smallReplies = usePreference(client.store, room, "small_replies")
|
const smallReplies = usePreference(client.store, room, "small_replies")
|
||||||
|
@ -58,21 +56,11 @@ const TimelineView = () => {
|
||||||
if (timelineViewRef.current) {
|
if (timelineViewRef.current) {
|
||||||
oldScrollHeight.current = timelineViewRef.current.scrollHeight
|
oldScrollHeight.current = timelineViewRef.current.scrollHeight
|
||||||
}
|
}
|
||||||
useLayoutEffect(() => {
|
|
||||||
const bottomRef = roomCtx.timelineBottomRef
|
|
||||||
if (bottomRef.current && roomCtx.scrolledToBottom) {
|
|
||||||
// For any timeline changes, if we were at the bottom, scroll to the new bottom
|
|
||||||
bottomRef.current.scrollIntoView()
|
|
||||||
} else if (timelineViewRef.current && prevOldestTimelineRow.current > (timeline[0]?.timeline_rowid ?? 0)) {
|
|
||||||
// When new entries are added to the top of the timeline, scroll down to keep the same position
|
|
||||||
timelineViewRef.current.scrollTop += timelineViewRef.current.scrollHeight - oldScrollHeight.current
|
|
||||||
}
|
|
||||||
prevOldestTimelineRow.current = timeline[0]?.timeline_rowid ?? 0
|
|
||||||
}, [client.userID, roomCtx, timeline])
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
roomCtx.directSetFocusedEventRowID = directSetFocusedEventRowID
|
roomCtx.directSetFocusedEventRowID = directSetFocusedEventRowID
|
||||||
}, [roomCtx])
|
}, [roomCtx])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
oldestTimelineRow.current = timeline[0]?.timeline_rowid ?? 0
|
||||||
const newestEvent = timeline[timeline.length - 1]
|
const newestEvent = timeline[timeline.length - 1]
|
||||||
if (
|
if (
|
||||||
roomCtx.scrolledToBottom
|
roomCtx.scrolledToBottom
|
||||||
|
@ -101,8 +89,11 @@ const TimelineView = () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const observer = new IntersectionObserver(entries => {
|
const observer = new IntersectionObserver(entries => {
|
||||||
if (entries[0]?.isIntersecting && room.paginationRequestedForRow !== prevOldestTimelineRow.current) {
|
if (
|
||||||
room.paginationRequestedForRow = prevOldestTimelineRow.current
|
entries[0]?.isIntersecting
|
||||||
|
&& room.paginationRequestedForRow !== oldestTimelineRow.current
|
||||||
|
) {
|
||||||
|
room.paginationRequestedForRow = oldestTimelineRow.current
|
||||||
loadHistory()
|
loadHistory()
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
@ -112,19 +103,12 @@ const TimelineView = () => {
|
||||||
})
|
})
|
||||||
observer.observe(topElem)
|
observer.observe(topElem)
|
||||||
return () => observer.unobserve(topElem)
|
return () => observer.unobserve(topElem)
|
||||||
}, [room, room.hasMoreHistory, loadHistory, oldestTimelineRow])
|
}, [room, room.hasMoreHistory, loadHistory])
|
||||||
|
|
||||||
let prevEvt: MemDBEvent | null = null
|
let prevEvt: MemDBEvent | null = null
|
||||||
return <div className="timeline-view" onScroll={handleScroll} ref={timelineViewRef}>
|
return <div className="timeline-view" onScroll={handleScroll} ref={timelineViewRef}>
|
||||||
<div className="timeline-beginning">
|
|
||||||
{room.hasMoreHistory ? <button onClick={loadHistory} disabled={isLoadingHistory}>
|
|
||||||
{isLoadingHistory
|
|
||||||
? <><ScaleLoader color="var(--primary-color)"/> Loading history...</>
|
|
||||||
: "Load more history"}
|
|
||||||
</button> : "No more history available in this room"}
|
|
||||||
</div>
|
|
||||||
<div className="timeline-list">
|
<div className="timeline-list">
|
||||||
<div className="timeline-top-ref" ref={topRef}/>
|
{/*<div className="timeline-bottom-ref" ref={bottomRef}/>*/}
|
||||||
{timeline.map(entry => {
|
{timeline.map(entry => {
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
return null
|
return null
|
||||||
|
@ -138,8 +122,15 @@ const TimelineView = () => {
|
||||||
/>
|
/>
|
||||||
prevEvt = entry
|
prevEvt = entry
|
||||||
return thisEvt
|
return thisEvt
|
||||||
})}
|
}).reverse()}
|
||||||
<div className="timeline-bottom-ref" ref={bottomRef}/>
|
<div className="timeline-top-ref" ref={topRef}/>
|
||||||
|
</div>
|
||||||
|
<div className="timeline-beginning">
|
||||||
|
{room.hasMoreHistory ? <button onClick={loadHistory} disabled={isLoadingHistory}>
|
||||||
|
{isLoadingHistory
|
||||||
|
? <><ScaleLoader color="var(--primary-color)"/> Loading history...</>
|
||||||
|
: "Load more history"}
|
||||||
|
</button> : "No more history available in this room"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue