mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 07:40:08 +09:00
a740581f20
The job picks the room's members once and then publishes to them one at a time, as the request did before it. A member who opens the room during that loop gets the read event in their other tabs and then the older notice, which marked the room unread again. Both events now carry the server time, and the sidebar ignores a notice dated before the room's latest read. The notice still moves a direct room to the top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142qgjggdJ2KDdGk7RF9Xm9
77 lines
2.0 KiB
JavaScript
77 lines
2.0 KiB
JavaScript
import { Controller } from "@hotwired/stimulus"
|
|
import { cable } from "@hotwired/turbo-rails"
|
|
import { ignoringBriefDisconnects } from "helpers/dom_helpers"
|
|
|
|
export default class extends Controller {
|
|
static targets = [ "room" ]
|
|
static classes = [ "unread" ]
|
|
|
|
#disconnected = true
|
|
#readAt = new Map()
|
|
|
|
async connect() {
|
|
this.channel ??= await cable.subscribeTo({ channel: "UnreadRoomsChannel" }, {
|
|
connected: this.#channelConnected.bind(this),
|
|
disconnected: this.#channelDisconnected.bind(this),
|
|
received: this.#unread.bind(this)
|
|
})
|
|
}
|
|
|
|
disconnect() {
|
|
ignoringBriefDisconnects(this.element, () => {
|
|
this.channel?.unsubscribe()
|
|
this.channel = null
|
|
})
|
|
}
|
|
|
|
loaded() {
|
|
this.read({ detail: { roomId: Current.room.id } })
|
|
}
|
|
|
|
read({ detail: { roomId, at } }) {
|
|
const room = this.#findRoomTarget(roomId)
|
|
|
|
if (at) {
|
|
this.#readAt.set(Number(roomId), Math.max(Number(at), this.#readAt.get(Number(roomId)) ?? 0))
|
|
}
|
|
|
|
if (room) {
|
|
room.classList.remove(this.unreadClass)
|
|
this.dispatch("read", { detail: { targetId: roomId } })
|
|
}
|
|
}
|
|
|
|
#channelConnected() {
|
|
if (this.#disconnected) {
|
|
this.#disconnected = false
|
|
this.element.reload()
|
|
}
|
|
}
|
|
|
|
#channelDisconnected() {
|
|
this.#disconnected = true
|
|
}
|
|
|
|
#unread({ roomId, at }) {
|
|
const unreadRoom = this.#findRoomTarget(roomId)
|
|
|
|
if (unreadRoom) {
|
|
if (Current.room.id != roomId && !this.#readSince(roomId, at)) {
|
|
unreadRoom.classList.add(this.unreadClass)
|
|
}
|
|
|
|
this.dispatch("unread", { detail: { targetId: unreadRoom.id } })
|
|
}
|
|
}
|
|
|
|
// Notices fan out one member at a time, so one can arrive after the member has already
|
|
// read the room in another tab. It still reorders the room, but doesn't mark it unread.
|
|
#readSince(roomId, at) {
|
|
return Number(at) <= this.#readAt.get(Number(roomId))
|
|
}
|
|
|
|
#findRoomTarget(roomId) {
|
|
return this.roomTargets.find(roomTarget => roomTarget.dataset.roomId == roomId)
|
|
}
|
|
}
|