diff --git a/src/js/chat.js b/src/js/chat.js index 9f8035f..050ff8b 100644 --- a/src/js/chat.js +++ b/src/js/chat.js @@ -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() diff --git a/src/js/timeline.js b/src/js/timeline.js index 0819ff8..270c6ca 100644 --- a/src/js/timeline.js +++ b/src/js/timeline.js @@ -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(`
Loading more... `) + 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} diff --git a/src/sass/loading.sass b/src/sass/loading.sass new file mode 100644 index 0000000..9705bbe --- /dev/null +++ b/src/sass/loading.sass @@ -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 diff --git a/src/sass/login.sass b/src/sass/login.sass index 235fad4..74d08ac 100644 --- a/src/sass/login.sass +++ b/src/sass/login.sass @@ -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 diff --git a/src/sass/main.sass b/src/sass/main.sass index d342bbb..150af73 100644 --- a/src/sass/main.sass +++ b/src/sass/main.sass @@ -5,3 +5,4 @@ @use "./components/chat" @use "./components/chat-input" @use "./components/anchor" +@use "./loading"