sitting33: 2 Don't mark messages as read when window is not in focus Show number of unread buffers or messages in title 4 files changed, 175 insertions(+), 38 deletions(-)
gamja/patches/.build.yml: SUCCESS in 52s [Don't mark messages as read when window is not in focus][0] v4 from [sitting33][1] [0]: https://lists.sr.ht/~emersion/public-inbox/patches/42525 [1]: mailto:me@sit.sh ✓ #1021731 SUCCESS gamja/patches/.build.yml https://builds.sr.ht/~emersion/job/1021731
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~emersion/public-inbox/patches/42525/mbox | git am -3Learn more about email & git
--- components/app.js | 65 ++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/components/app.js b/components/app.js index 69098d9..59c6c44 100644 --- a/components/app.js +++ b/components/app.js @@ -529,33 +529,13 @@ export default class App extends Component { client.setReadMarker(storedBuffer.name, readReceipt.time); } - switchBuffer(id) { - let buf; - this.setState((state) => { - buf = State.getBuffer(state, id); + markBufferAsRead(id) { + this.setState(state => { + const buf = State.getBuffer(state, id); if (!buf) { return; } - let client = this.clients.get(buf.server); - let stored = this.bufferStore.get({ name: buf.name, server: client.params }); - let prevReadReceipt = getReceipt(stored, ReceiptType.READ); - // TODO: only mark as read if user scrolled at the bottom - let update = State.updateBuffer(state, buf.id, { - unread: Unread.NONE, - prevReadReceipt, - }); - - return { ...update, activeBuffer: buf.id }; - }, () => { - if (!buf) { - return; - } - - if (this.buffer.current) { - this.buffer.current.focus(); - } - let client = this.clients.get(buf.server); for (let notif of this.messageNotifications) { @@ -577,6 +557,38 @@ export default class App extends Component { } } + return State.updateBuffer(state, buf.id, { + unread: Unread.NONE, + }); + }); + } + + switchBuffer(id) { + let buf; + this.setState((state) => { + buf = State.getBuffer(state, id); + if (!buf) { + return; + } + + let client = this.clients.get(buf.server); + let stored = this.bufferStore.get({ name: buf.name, server: client.params }); + let prevReadReceipt = getReceipt(stored, ReceiptType.READ); + + let update = State.updateBuffer(state, buf.id, { + prevReadReceipt, + }); + + return { ...update, activeBuffer: buf.id }; + }, () => { + if (!buf) { + return; + } + + if (this.buffer.current) { + this.buffer.current.focus(); + } + let server = this.state.servers.get(buf.server); if (buf.type === BufferType.NICK && !server.users.has(buf.name)) { this.whoUserBuffer(buf.name, buf.server); @@ -591,6 +603,9 @@ export default class App extends Component { } else { document.title = this.baseTitle; } + + // TODO: only mark as read if user scrolled at the bottom + this.markBufferAsRead(buf.id); }); } @@ -718,7 +733,7 @@ export default class App extends Component { let prevReadReceipt = buf.prevReadReceipt; let receipts = { [ReceiptType.DELIVERED]: receiptFromMessage(msg) }; - if (this.state.activeBuffer !== buf.id) { + if (this.state.activeBuffer !== buf.id || !document.hasFocus()) { unread = Unread.union(unread, msgUnread); } else { receipts[ReceiptType.READ] = receiptFromMessage(msg); @@ -1919,6 +1934,8 @@ export default class App extends Component { for (let client of this.clients.values()) { client.send({ command: "PING", params: ["gamja"] }); } + + this.markBufferAsRead(this.state.activeBuffer); } componentDidMount() { -- 2.41.0
--- components/app.js | 105 +++++++++++++++++++++++++++++++----- components/settings-form.js | 42 +++++++++++++++ state.js | 1 + 3 files changed, 134 insertions(+), 14 deletions(-) diff --git a/components/app.js b/components/app.js index 59c6c44..2838c5a 100644 --- a/components/app.js +++ b/components/app.js @@ -560,6 +560,8 @@ export default class App extends Component { return State.updateBuffer(state, buf.id, { unread: Unread.NONE, }); + }, () => { + this.updateDocumentTitle(); }); } @@ -598,17 +600,70 @@ export default class App extends Component { this.whoChannelBuffer(buf.name, buf.server); } - if (buf.type !== BufferType.SERVER) { - document.title = buf.name + ' · ' + this.baseTitle; - } else { - document.title = this.baseTitle; - } - // TODO: only mark as read if user scrolled at the bottom this.markBufferAsRead(buf.id); }); } + updateDocumentTitle() { + const currentBuffer = State.getBuffer(this.state, this.state.activeBuffer); + const currentServerName = getServerName(this.state.servers.get(currentBuffer.server)); + + let resultingTitle = ""; + + // add unread counter + let titleCounterSetting = this.state.settings.titleCounter; + let unreadCounter = 0; + if (titleCounterSetting === "buffers" || titleCounterSetting === "highlights") { + let minUnread = titleCounterSetting === "highlights" ? Unread.HIGHLIGHT : Unread.MESSAGE; + for (const buffer of this.state.buffers.values()) { + if (Unread.compare(buffer.unread, minUnread) >= 0) { + unreadCounter++; + } + } + } else if (titleCounterSetting === "messages") { + for (const buffer of this.state.buffers.values()) { + if (buffer.unread === Unread.NONE) { + continue; + } + + const client = this.clients.get(buffer.server); + const stored = this.bufferStore.get({name: buffer.name, server: client.params}); + const lastReadReceipt = getReceipt(stored, ReceiptType.READ); + + // go through every message starting at the bottom until we reach + // a message that's already been read + let bufferUnreadCounter = 0; + let msgIndex = buffer.messages.length - 1; + let msg = buffer.messages[msgIndex]; + while (msg && !isMessageBeforeReceipt(msg, lastReadReceipt)) { + // these are messages that can trigger a buffer to be marked as unread + if (["PRIVMSG", "NOTICE", "INVITE"].includes(msg.command)) { + bufferUnreadCounter++; + } + msg = buffer.messages[--msgIndex]; + } + + unreadCounter += Math.max(bufferUnreadCounter, 1); + } + } + if (unreadCounter > 0) { + resultingTitle += `(${unreadCounter}) `; + } + + // add current buffer name + if (currentBuffer.type !== BufferType.SERVER) { + resultingTitle += currentBuffer.name + ' · '; + } + + resultingTitle += currentServerName + ' · '; + + // add the base title (document.title) + resultingTitle += this.baseTitle; + + document.title = resultingTitle; + } + prepareChatMessage(serverID, msg) { // Treat server-wide broadcasts as highlights. They're sent by server // operators and can contain important information. @@ -754,6 +809,11 @@ export default class App extends Component { this.sendReadReceipt(client, stored); } return { unread, prevReadReceipt }; + }, () => { + // don't update the title if we're getting the history + if (!irc.findBatchByType(msg, "chathistory")) { + this.updateDocumentTitle(); + } }); } @@ -1153,17 +1213,30 @@ export default class App extends Component { } let name = msg.params[0].slice(1); let batch = client.batches.get(name); - if (!batch || batch.type !== "soju.im/bouncer-networks") { + if (!batch) { break; } - // We've received a BOUNCER NETWORK batch. If we have a URL to - // auto-open and no existing network matches it, ask the user to - // create a new network. - if (this.autoOpenURL && this.autoOpenURL.host && !this.findBouncerNetIDByHost(this.autoOpenURL.host)) { - this.openURL(this.autoOpenURL); - this.autoOpenURL = null; + switch (batch.type) { + case "chathistory": + // Since the component hasn't updated because of the new + // messages yet, we need to update the title *after* the + // new state has been applied. + this.setState({}, () => { + this.updateDocumentTitle(); + }) + break; + case "soju.im/bouncer-networks": + // We've received a BOUNCER NETWORK batch. If we have a URL to + // auto-open and no existing network matches it, ask the user to + // create a new network. + if (this.autoOpenURL && this.autoOpenURL.host && !this.findBouncerNetIDByHost(this.autoOpenURL.host)) { + this.openURL(this.autoOpenURL); + this.autoOpenURL = null; + } + break; } + break; case "MARKREAD": target = msg.params[0]; @@ -1217,6 +1290,8 @@ export default class App extends Component { closed, receipts: { [ReceiptType.READ]: readReceipt }, }); + + this.updateDocumentTitle(); }); break; default: @@ -1913,7 +1988,9 @@ export default class App extends Component { handleSettingsChange(settings) { store.settings.put(settings); - this.setState({ settings }); + this.setState({ settings }, () => { + this.updateDocumentTitle(); + }); } handleSettingsDisconnect() { diff --git a/components/settings-form.js b/components/settings-form.js index 31e045e..3044eb1 100644 --- a/components/settings-form.js +++ b/components/settings-form.js @@ -8,6 +8,7 @@ export default class SettingsForm extends Component { this.state.secondsInTimestamps = props.settings.secondsInTimestamps; this.state.bufferEvents = props.settings.bufferEvents; + this.state.titleCounter = props.settings.titleCounter; this.handleInput = this.handleInput.bind(this); this.handleSubmit = this.handleSubmit.bind(this); @@ -67,6 +68,47 @@ export default class SettingsForm extends Component { </label> <br/><br/> + <label> + <input + type="radio" + name="titleCounter" + value="none" + checked=${this.state.titleCounter === "none"} + /> + Don't show unread counter in page title + </label> + <br/> + <label> + <input + type="radio" + name="titleCounter" + value="highlights" + checked=${this.state.titleCounter === "highlights"} + /> + Show number of highlights in page title + </label> + <br/> + <label> + <input + type="radio" + name="titleCounter" + value="buffers" + checked=${this.state.titleCounter === "buffers"} + /> + Show number of all unread buffers in page title + </label> + <br/> + <label> + <input + type="radio" + name="titleCounter" + value="messages" + checked=${this.state.titleCounter === "messages"} + /> + Show number of all unread messages in page title + </label> + <br/><br/> + <label> <input type="radio" diff --git a/state.js b/state.js index 1c1bc5a..ff25d47 100644 --- a/state.js +++ b/state.js @@ -213,6 +213,7 @@ export const State = { settings: { secondsInTimestamps: true, bufferEvents: BufferEventsDisplayMode.FOLD, + titleCounter: "highlights", }, }; }, -- 2.41.0
builds.sr.ht <builds@sr.ht>gamja/patches/.build.yml: SUCCESS in 52s [Don't mark messages as read when window is not in focus][0] v4 from [sitting33][1] [0]: https://lists.sr.ht/~emersion/public-inbox/patches/42525 [1]: mailto:me@sit.sh ✓ #1021731 SUCCESS gamja/patches/.build.yml https://builds.sr.ht/~emersion/job/1021731