Add scrollback
This commit is contained in:
parent
51905ab3f2
commit
6227f6fa84
5 changed files with 88 additions and 36 deletions
|
@ -27,7 +27,7 @@ class Chat extends ElemJS {
|
|||
// connect to the new room's timeline updater
|
||||
if (store.activeRoom.exists()) {
|
||||
const timeline = store.activeRoom.value().timeline
|
||||
const subscription = () => {
|
||||
const beforeChangeSubscription = () => {
|
||||
// scroll anchor does not work if the timeline is scrolled to the top.
|
||||
// at the start, when there are not enough messages for a full screen, this is the case.
|
||||
// once there are enough messages that scrolling is necessary, we initiate a scroll down to activate the scroll anchor.
|
||||
|
@ -40,12 +40,29 @@ class Chat extends ElemJS {
|
|||
}
|
||||
}, 0)
|
||||
}
|
||||
const name = "beforeChange"
|
||||
this.removableSubscriptions.push({name, target: timeline, subscription})
|
||||
timeline.subscribe(name, subscription)
|
||||
this.addSubscription("beforeChange", timeline, beforeChangeSubscription)
|
||||
|
||||
//Make sure after loading scrollback we don't move the scroll position
|
||||
const beforeScrollbackLoadSubscription = () => {
|
||||
const lastScrollHeight = chatMessages.scrollHeight;
|
||||
|
||||
const afterScrollbackLoadSub = () => {
|
||||
const scrollDiff = chatMessages.scrollHeight - lastScrollHeight;
|
||||
chatMessages.scrollTop += scrollDiff;
|
||||
|
||||
timeline.unsubscribe("afterScrollbackLoad", afterScrollbackLoadSub)
|
||||
}
|
||||
|
||||
timeline.subscribe("afterScrollbackLoad", afterScrollbackLoadSub)
|
||||
}
|
||||
this.addSubscription("beforeScrollbackLoad", timeline, beforeScrollbackLoadSubscription)
|
||||
}
|
||||
this.render()
|
||||
}
|
||||
addSubscription(name, target, subscription) {
|
||||
this.removableSubscriptions.push({name, target, subscription})
|
||||
target.subscribe(name, subscription)
|
||||
}
|
||||
|
||||
render() {
|
||||
this.clearChildren()
|
||||
|
|
|
@ -176,12 +176,32 @@ class EventGroup extends ElemJS {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//Displays a spiner and creates an event to notify timeline to load more messages
|
||||
class LoadMore extends ElemJS {
|
||||
constructor() {
|
||||
super("div")
|
||||
this.html(`<div class="loading-icon"></div> <span> Loading more... </span>`)
|
||||
const intersection_observer = new IntersectionObserver(e => this.intersectionHandler(e))
|
||||
intersection_observer.observe(this.element)
|
||||
|
||||
}
|
||||
|
||||
intersectionHandler(e) {
|
||||
if (e.some(e => e.isIntersecting)) {
|
||||
const event = new CustomEvent("LoadMore", {bubbles: true})
|
||||
this.element.dispatchEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReactiveTimeline extends ElemJS {
|
||||
constructor(id, list) {
|
||||
super("div")
|
||||
this.class("c-event-groups")
|
||||
this.id = id
|
||||
this.list = list
|
||||
this.load_more = new LoadMore()
|
||||
this.render()
|
||||
}
|
||||
|
||||
|
@ -201,6 +221,9 @@ class ReactiveTimeline extends ElemJS {
|
|||
} else {
|
||||
this.tryAddGroups(event, [search.i])
|
||||
}
|
||||
this.load_more.remove()
|
||||
this.load_more = new LoadMore()
|
||||
this.childAt(0, this.load_more)
|
||||
}
|
||||
|
||||
tryAddGroups(event, indices) {
|
||||
|
@ -233,6 +256,7 @@ class ReactiveTimeline extends ElemJS {
|
|||
|
||||
render() {
|
||||
this.clearChildren()
|
||||
this.child(this.load_more)
|
||||
this.list.forEach(group => this.child(group))
|
||||
this.anchor = new Anchor()
|
||||
this.child(this.anchor)
|
||||
|
@ -244,11 +268,15 @@ class Timeline extends Subscribable {
|
|||
super()
|
||||
Object.assign(this.events, {
|
||||
beforeChange: [],
|
||||
afterChange: []
|
||||
afterChange: [],
|
||||
beforeScrollbackLoad: [],
|
||||
afterScrollbackLoad: [],
|
||||
})
|
||||
Object.assign(this.eventDeps, {
|
||||
beforeChange: [],
|
||||
afterChange: []
|
||||
afterChange: [],
|
||||
beforeScrollbackLoad: [],
|
||||
afterScrollbackLoad: [],
|
||||
})
|
||||
this.room = room
|
||||
this.id = this.room.id
|
||||
|
@ -259,6 +287,8 @@ class Timeline extends Subscribable {
|
|||
this.pending = new Set()
|
||||
this.pendingEdits = []
|
||||
this.from = null
|
||||
|
||||
this.reactiveTimeline.element.addEventListener("LoadMore", () => this.loadScrollback())
|
||||
}
|
||||
|
||||
updateStateEvents(events) {
|
||||
|
@ -343,6 +373,7 @@ class Timeline extends Subscribable {
|
|||
}
|
||||
|
||||
async loadScrollback() {
|
||||
this.broadcast("beforeScrollbackLoad")
|
||||
debug = true
|
||||
if (!this.from) throw new Error("Can't load scrollback, no from token")
|
||||
const url = new URL(`${lsm.get("domain")}/_matrix/client/r0/rooms/${this.id}/messages`)
|
||||
|
@ -356,9 +387,10 @@ class Timeline extends Subscribable {
|
|||
url.searchParams.set("filter", JSON.stringify(filter))
|
||||
const root = await fetch(url.toString()).then(res => res.json())
|
||||
this.from = root.end
|
||||
console.log(this.updateEvents, root.chunk)
|
||||
//console.log(this.updateEvents, root.chunk)
|
||||
if (root.state) this.updateStateEvents(root.state)
|
||||
this.updateEvents(root.chunk)
|
||||
this.broadcast("afterScrollbackLoad")
|
||||
}
|
||||
|
||||
send(body) {
|
||||
|
@ -393,24 +425,24 @@ class Timeline extends Subscribable {
|
|||
this.subscribe("afterChange", subscription)
|
||||
})*/
|
||||
}
|
||||
/*
|
||||
getGroupedEvents() {
|
||||
let currentSender = Symbol("N/A")
|
||||
let groups = []
|
||||
let currentGroup = []
|
||||
for (const event of this.list) {
|
||||
if (event.sender === currentSender) {
|
||||
currentGroup.push(event)
|
||||
} else {
|
||||
if (currentGroup.length) groups.push(currentGroup)
|
||||
currentGroup = [event]
|
||||
currentSender = event.sender
|
||||
/*
|
||||
getGroupedEvents() {
|
||||
let currentSender = Symbol("N/A")
|
||||
let groups = []
|
||||
let currentGroup = []
|
||||
for (const event of this.list) {
|
||||
if (event.sender === currentSender) {
|
||||
currentGroup.push(event)
|
||||
} else {
|
||||
if (currentGroup.length) groups.push(currentGroup)
|
||||
currentGroup = [event]
|
||||
currentSender = event.sender
|
||||
}
|
||||
}
|
||||
if (currentGroup.length) groups.push(currentGroup)
|
||||
return groups
|
||||
}
|
||||
if (currentGroup.length) groups.push(currentGroup)
|
||||
return groups
|
||||
}
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
module.exports = {Timeline}
|
||||
|
|
13
src/sass/loading.sass
Normal file
13
src/sass/loading.sass
Normal file
|
@ -0,0 +1,13 @@
|
|||
@keyframes spin
|
||||
0%
|
||||
transform: rotate(0deg)
|
||||
100%
|
||||
transform: rotate(180deg)
|
||||
|
||||
.loading-icon
|
||||
display: inline-block
|
||||
background-color: #ccc
|
||||
width: 12px
|
||||
height: 12px
|
||||
margin-right: 6px
|
||||
animation: spin 0.7s infinite
|
|
@ -1,6 +1,8 @@
|
|||
@use "./base"
|
||||
@use "./loading.sass"
|
||||
@use "./colors.sass" as c
|
||||
|
||||
|
||||
.main
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
@ -41,19 +43,6 @@
|
|||
.form-error
|
||||
color: red
|
||||
|
||||
@keyframes spin
|
||||
0%
|
||||
transform: rotate(0deg)
|
||||
100%
|
||||
transform: rotate(180deg)
|
||||
|
||||
.loading-icon
|
||||
display: inline-block
|
||||
background-color: #ccc
|
||||
width: 12px
|
||||
height: 12px
|
||||
margin-right: 6px
|
||||
animation: spin 0.7s infinite
|
||||
|
||||
input, button
|
||||
font-family: inherit
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
@use "./components/chat"
|
||||
@use "./components/chat-input"
|
||||
@use "./components/anchor"
|
||||
@use "./loading"
|
||||
|
|
Loading…
Reference in a new issue