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
 | 
							// connect to the new room's timeline updater
 | 
				
			||||||
		if (store.activeRoom.exists()) {
 | 
							if (store.activeRoom.exists()) {
 | 
				
			||||||
			const timeline = store.activeRoom.value().timeline
 | 
								const timeline = store.activeRoom.value().timeline
 | 
				
			||||||
			const subscription = () => {
 | 
								const beforeChangeSubscription = () => {
 | 
				
			||||||
				// scroll anchor does not work if the timeline is scrolled to the top.
 | 
									// 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.
 | 
									// 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.
 | 
									// 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)
 | 
									}, 0)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			const name = "beforeChange"
 | 
								this.addSubscription("beforeChange", timeline, beforeChangeSubscription)
 | 
				
			||||||
			this.removableSubscriptions.push({name, target: timeline, subscription})
 | 
					
 | 
				
			||||||
			timeline.subscribe(name, subscription)
 | 
								//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()
 | 
							this.render()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						addSubscription(name, target, subscription) {
 | 
				
			||||||
 | 
							this.removableSubscriptions.push({name, target, subscription})
 | 
				
			||||||
 | 
							target.subscribe(name, subscription)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	render() {
 | 
						render() {
 | 
				
			||||||
		this.clearChildren()
 | 
							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 {
 | 
					class ReactiveTimeline extends ElemJS {
 | 
				
			||||||
	constructor(id, list) {
 | 
						constructor(id, list) {
 | 
				
			||||||
		super("div")
 | 
							super("div")
 | 
				
			||||||
		this.class("c-event-groups")
 | 
							this.class("c-event-groups")
 | 
				
			||||||
		this.id = id
 | 
							this.id = id
 | 
				
			||||||
		this.list = list
 | 
							this.list = list
 | 
				
			||||||
 | 
							this.load_more = new LoadMore()
 | 
				
			||||||
		this.render()
 | 
							this.render()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -201,6 +221,9 @@ class ReactiveTimeline extends ElemJS {
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			this.tryAddGroups(event, [search.i])
 | 
								this.tryAddGroups(event, [search.i])
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							this.load_more.remove()
 | 
				
			||||||
 | 
							this.load_more = new LoadMore()
 | 
				
			||||||
 | 
							this.childAt(0, this.load_more)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tryAddGroups(event, indices) {
 | 
						tryAddGroups(event, indices) {
 | 
				
			||||||
| 
						 | 
					@ -233,6 +256,7 @@ class ReactiveTimeline extends ElemJS {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	render() {
 | 
						render() {
 | 
				
			||||||
		this.clearChildren()
 | 
							this.clearChildren()
 | 
				
			||||||
 | 
							this.child(this.load_more)
 | 
				
			||||||
		this.list.forEach(group => this.child(group))
 | 
							this.list.forEach(group => this.child(group))
 | 
				
			||||||
		this.anchor = new Anchor()
 | 
							this.anchor = new Anchor()
 | 
				
			||||||
		this.child(this.anchor)
 | 
							this.child(this.anchor)
 | 
				
			||||||
| 
						 | 
					@ -244,11 +268,15 @@ class Timeline extends Subscribable {
 | 
				
			||||||
		super()
 | 
							super()
 | 
				
			||||||
		Object.assign(this.events, {
 | 
							Object.assign(this.events, {
 | 
				
			||||||
			beforeChange: [],
 | 
								beforeChange: [],
 | 
				
			||||||
			afterChange: []
 | 
								afterChange: [],
 | 
				
			||||||
 | 
								beforeScrollbackLoad: [],
 | 
				
			||||||
 | 
								afterScrollbackLoad: [],
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		Object.assign(this.eventDeps, {
 | 
							Object.assign(this.eventDeps, {
 | 
				
			||||||
			beforeChange: [],
 | 
								beforeChange: [],
 | 
				
			||||||
			afterChange: []
 | 
								afterChange: [],
 | 
				
			||||||
 | 
								beforeScrollbackLoad: [],
 | 
				
			||||||
 | 
								afterScrollbackLoad: [],
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		this.room = room
 | 
							this.room = room
 | 
				
			||||||
		this.id = this.room.id
 | 
							this.id = this.room.id
 | 
				
			||||||
| 
						 | 
					@ -259,6 +287,8 @@ class Timeline extends Subscribable {
 | 
				
			||||||
		this.pending = new Set()
 | 
							this.pending = new Set()
 | 
				
			||||||
		this.pendingEdits = []
 | 
							this.pendingEdits = []
 | 
				
			||||||
		this.from = null
 | 
							this.from = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.reactiveTimeline.element.addEventListener("LoadMore", () => this.loadScrollback())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	updateStateEvents(events) {
 | 
						updateStateEvents(events) {
 | 
				
			||||||
| 
						 | 
					@ -343,6 +373,7 @@ class Timeline extends Subscribable {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async loadScrollback() {
 | 
						async loadScrollback() {
 | 
				
			||||||
 | 
							this.broadcast("beforeScrollbackLoad")
 | 
				
			||||||
		debug = true
 | 
							debug = true
 | 
				
			||||||
		if (!this.from) throw new Error("Can't load scrollback, no from token")
 | 
							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`)
 | 
							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))
 | 
							url.searchParams.set("filter", JSON.stringify(filter))
 | 
				
			||||||
		const root = await fetch(url.toString()).then(res => res.json())
 | 
							const root = await fetch(url.toString()).then(res => res.json())
 | 
				
			||||||
		this.from = root.end
 | 
							this.from = root.end
 | 
				
			||||||
		console.log(this.updateEvents, root.chunk)
 | 
							//console.log(this.updateEvents, root.chunk)
 | 
				
			||||||
		if (root.state) this.updateStateEvents(root.state)
 | 
							if (root.state) this.updateStateEvents(root.state)
 | 
				
			||||||
		this.updateEvents(root.chunk)
 | 
							this.updateEvents(root.chunk)
 | 
				
			||||||
 | 
							this.broadcast("afterScrollbackLoad")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	send(body) {
 | 
						send(body) {
 | 
				
			||||||
| 
						 | 
					@ -393,24 +425,24 @@ class Timeline extends Subscribable {
 | 
				
			||||||
			this.subscribe("afterChange", subscription)
 | 
								this.subscribe("afterChange", subscription)
 | 
				
			||||||
		})*/
 | 
							})*/
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
/*
 | 
						/*
 | 
				
			||||||
	getGroupedEvents() {
 | 
							getGroupedEvents() {
 | 
				
			||||||
		let currentSender = Symbol("N/A")
 | 
								let currentSender = Symbol("N/A")
 | 
				
			||||||
		let groups = []
 | 
								let groups = []
 | 
				
			||||||
		let currentGroup = []
 | 
								let currentGroup = []
 | 
				
			||||||
		for (const event of this.list) {
 | 
								for (const event of this.list) {
 | 
				
			||||||
			if (event.sender === currentSender) {
 | 
									if (event.sender === currentSender) {
 | 
				
			||||||
				currentGroup.push(event)
 | 
										currentGroup.push(event)
 | 
				
			||||||
			} else {
 | 
									} else {
 | 
				
			||||||
				if (currentGroup.length) groups.push(currentGroup)
 | 
										if (currentGroup.length) groups.push(currentGroup)
 | 
				
			||||||
				currentGroup = [event]
 | 
										currentGroup = [event]
 | 
				
			||||||
				currentSender = event.sender
 | 
										currentSender = event.sender
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if (currentGroup.length) groups.push(currentGroup)
 | 
				
			||||||
 | 
								return groups
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (currentGroup.length) groups.push(currentGroup)
 | 
							*/
 | 
				
			||||||
		return groups
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	*/
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {Timeline}
 | 
					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 "./base"
 | 
				
			||||||
 | 
					@use "./loading.sass"
 | 
				
			||||||
@use "./colors.sass" as c
 | 
					@use "./colors.sass" as c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.main
 | 
					.main
 | 
				
			||||||
  justify-content: center
 | 
					  justify-content: center
 | 
				
			||||||
  align-items: center
 | 
					  align-items: center
 | 
				
			||||||
| 
						 | 
					@ -41,19 +43,6 @@
 | 
				
			||||||
.form-error
 | 
					.form-error
 | 
				
			||||||
  color: red
 | 
					  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
 | 
					input, button
 | 
				
			||||||
  font-family: inherit
 | 
					  font-family: inherit
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,3 +5,4 @@
 | 
				
			||||||
@use "./components/chat"
 | 
					@use "./components/chat"
 | 
				
			||||||
@use "./components/chat-input"
 | 
					@use "./components/chat-input"
 | 
				
			||||||
@use "./components/anchor"
 | 
					@use "./components/anchor"
 | 
				
			||||||
 | 
					@use "./loading"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue