Display unread/notification counters on rooms
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
e90a2c7da8
commit
229e6903fd
7 changed files with 171 additions and 32 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue