diff --git a/src/js/room-picker.js b/src/js/room-picker.js index 3046612..810f5ee 100644 --- a/src/js/room-picker.js +++ b/src/js/room-picker.js @@ -56,12 +56,65 @@ class Group extends ElemJS { } } +class RoomNotifier extends ElemJS { + constructor() { + super("div") + + this.classes = [ + "notifications", + "unreads", + "none" + ] + + this.class("c-room__number") + this.state = { + notifications: 0, + unreads: 0 + } + this.render() + } + + /** + * @param {object} state + * @param {number} [state.notifications] + * @param {number} [state.unreads] + */ + update(state) { + Object.assign(this.state, state) + this.render() + } + + render() { + const display = { + number: this.state.notifications || this.state.unreads, + kind: this.state.notifications ? "notifications" : "unreads" + } + // set number + if (display.number) { + this.text(display.number) + } else { + this.text("") + display.kind = "none" + } + // set class + this.classes.forEach(c => { + const name = "c-room__number--" + c + if (c === display.kind) { + this.class(name) + } else { + this.removeClass(name) + } + }) + } +} + class Room extends ElemJS { constructor(id, data) { super("div") this.id = id this.data = data + this.number = new RoomNotifier() this.timeline = new Timeline(this) this.group = null this.members = new SubscribeMapList(SubscribeValue) @@ -75,22 +128,21 @@ class Room extends ElemJS { } get order() { - if (this.group) { - let chars = 36 - let total = 0 - const name = this.getName() - for (let i = 0; i < name.length; i++) { - const c = name[i] - let d = 0 - if (c >= "A" && c <= "Z") d = c.charCodeAt(0) - 65 + 10 - else if (c >= "a" && c <= "z") d = c.charCodeAt(0) - 97 + 10 - else if (c >= "0" && c <= "9") d = +c - total += d * chars ** (-i) - } - return total + let string = "" + if (this.number.state.notifications) { + string += "N" + } else if (this.number.state.unreads) { + string += "U" } else { - return -this.timeline.latest + string += "_" } + if (this.group) { + string += this.name + } else { + string += (4000000000000 - this.timeline.latest) // good until 2065 :) + } + console.log(string) + return string } getMemberName(mxid) { @@ -174,6 +226,7 @@ class Room extends ElemJS { this.child(ejs("div").class("c-room__icon", "c-room__icon--no-icon")) } this.child(ejs("div").class("c-room__name").text(this.getName())) + this.child(this.number) // active const active = store.activeRoom.value() === this this.element.classList[active ? "add" : "remove"]("c-room--active") @@ -193,6 +246,7 @@ class Rooms extends ElemJS { store.activeGroup.subscribe("changeSelf", this.render.bind(this)) store.directs.subscribe("changeItem", this.render.bind(this)) store.newEvents.subscribe("changeSelf", this.sort.bind(this)) + store.notificationsChange.subscribe("changeSelf", this.sort.bind(this)) this.render() } diff --git a/src/js/store/store.js b/src/js/store/store.js index e11905f..8ef4511 100644 --- a/src/js/store/store.js +++ b/src/js/store/store.js @@ -9,7 +9,8 @@ const store = { directs: new SubscribeSet(), activeGroup: new SubscribeValue(), activeRoom: new SubscribeValue(), - newEvents: new Subscribable() + newEvents: new Subscribable(), + notificationsChange: new Subscribable() } window.store = store diff --git a/src/js/store/subscribe_map.js b/src/js/store/subscribe_map.js index 9ee3eac..a15d4e2 100644 --- a/src/js/store/subscribe_map.js +++ b/src/js/store/subscribe_map.js @@ -1,40 +1,54 @@ const {Subscribable} = require("./subscribable.js") -const {SubscribeValue} = require("./subscribe_value.js") class SubscribeMap extends Subscribable { constructor() { super() Object.assign(this.events, { addItem: [], + editItem: [], + deleteItem: [], changeItem: [], - removeItem: [] + askSet: [] }) - this.map = new Map() + Object.assign(this.eventDeps, { + addItem: ["changeItem"], + editItem: ["changeItem"], + deleteItem: ["changeItem"], + changeItem: [], + askSet: [] + }) + this.backing = new Map() } has(key) { - return this.map.has(key) && this.map.get(key).exists() + return this.backing.has(key) } - get(key) { - if (this.map.has(key)) { - return this.map.get(key) - } else { - this.map.set(key, new SubscribeValue()) + forEach(f) { + for (const key of this.backing.keys()) { + f(key, this.backing.get(key)) } } + askSet(key, value) { + this.broadcast("askSet", key, value) + } + set(key, value) { - let s - if (this.map.has(key)) { - s = this.map.get(key).set(value) - this.broadcast("changeItem", key) - } else { - s = new SubscribeValue().set(value) - this.map.set(key, s) + const existed = this.backing.has(key) + this.backing.set(key, value) + if (existed) { this.broadcast("addItem", key) + } else { + this.broadcast("editItem", key) + } + } + + delete(key) { + if (this.backing.has(key)) { + this.backing.delete(key) + this.broadcast("deleteItem", key) } - return s } } diff --git a/src/js/store/subscribe_map_list.js b/src/js/store/subscribe_map_list.js index 28a5ce2..dfbdc6c 100644 --- a/src/js/store/subscribe_map_list.js +++ b/src/js/store/subscribe_map_list.js @@ -54,6 +54,15 @@ class SubscribeMapList extends Subscribable { } sort() { + const key = this.list[0] + if (typeof this.map.get(key).value().order === "number") { + this.sortByNumber() + } else { + this.sortByString() + } + } + + sortByNumber() { this.list.sort((a, b) => { const orderA = this.map.get(a).value().order const orderB = this.map.get(b).value().order @@ -62,6 +71,20 @@ class SubscribeMapList extends Subscribable { this.broadcast("changeItem") } + sortByString() { + this.list.sort((a, b) => { + let r + const orderA = this.map.get(a).value().order + const orderB = this.map.get(b).value().order + if (orderA < orderB) r = -1 + else if (orderA > orderB) r = 1 + else r = 0 + console.log("comparing", orderA, orderB, r) + return r + }) + this.broadcast("changeItem") + } + _add(key, value, start) { let s if (this.map.has(key)) { diff --git a/src/js/sync/sync.js b/src/js/sync/sync.js index 7434a79..90c92b9 100644 --- a/src/js/sync/sync.js +++ b/src/js/sync/sync.js @@ -37,6 +37,7 @@ function sync() { function manageSync(root) { try { let newEvents = false + let notificationsChange = false // set up directs if (root.account_data) { @@ -66,6 +67,14 @@ function manageSync(root) { } } if (data.ephemeral) timeline.updateEphemeral(data.ephemeral.events) + if (data.unread_notifications) { + timeline.updateNotificationCount(data.unread_notifications.notification_count) + notificationsChange = true + } + if (data["org.matrix.msc2654.unread_count"] != undefined) { + timeline.updateUnreadCount(data["org.matrix.msc2654.unread_count"]) + notificationsChange = true + } }) } } @@ -109,6 +118,7 @@ function manageSync(root) { } if (newEvents) store.newEvents.broadcast("changeSelf") + if (notificationsChange) store.notificationsChange.broadcast("changeSelf") } catch (e) { console.error(root) throw e diff --git a/src/js/timeline.js b/src/js/timeline.js index 63f7a04..fa797da 100644 --- a/src/js/timeline.js +++ b/src/js/timeline.js @@ -1,6 +1,7 @@ const {ElemJS, ejs} = require("./basic.js") const {Subscribable} = require("./store/subscribable.js") const {SubscribeValue} = require("./store/subscribe_value.js") +const {SubscribeMap} = require("./store/subscribe_map.js") const {store} = require("./store/store.js") const {Anchor} = require("./anchor.js") const {Sender} = require("./sender.js") @@ -197,6 +198,7 @@ class Timeline extends Subscribable { this.pending = new Set() this.pendingEdits = [] this.typing = new SubscribeValue().set([]) + this.userReads = new SubscribeMap() this.from = null } @@ -280,9 +282,25 @@ class Timeline extends Subscribable { if (eventData.type === "m.typing") { this.typing.set(eventData.content.user_ids) } + if (eventData.type === "m.receipt") { + for (const eventID of Object.keys(eventData.content)) { + for (const user of Object.keys(eventData.content[eventID]["m.read"])) { + this.userReads.set(user, eventID) + } + } + // console.log("Updated read receipts:", this.userReads) + } } } + updateUnreadCount(count) { + this.room.number.update({unreads: count}) + } + + updateNotificationCount(count) { + this.room.number.update({notifications: count}) + } + removeEvent(id) { if (!this.map.has(id)) throw new Error(`Tried to delete event ID ${id} which does not exist`) this.map.get(id).removeEvent() diff --git a/src/sass/components/rooms.sass b/src/sass/components/rooms.sass index 462e13c..a8bca58 100644 --- a/src/sass/components/rooms.sass +++ b/src/sass/components/rooms.sass @@ -43,3 +43,22 @@ $icon-padding: 8px white-space: nowrap overflow: hidden text-overflow: ellipsis + flex: 1 + + &__number + flex-shrink: 0 + line-height: 1 + padding: 4px 5px + border-radius: 5px + font-size: 14px + + &--none + display: none + + &--unreads + background-color: #ddd + color: #111 + + &--notifications + background-color: #ffac4b + color: #000