Add scrollback #22
					 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue