hicli/database,web/roomlist: show marked unread status

This commit is contained in:
Tulir Asokan 2024-11-16 23:45:31 +02:00
parent ead1365c12
commit 80f9a8bb6b
11 changed files with 49 additions and 9 deletions

View file

@ -23,7 +23,7 @@ const (
getRoomBaseQuery = ` getRoomBaseQuery = `
SELECT room_id, creation_content, tombstone_content, name, name_quality, avatar, explicit_avatar, topic, canonical_alias, SELECT room_id, creation_content, tombstone_content, name, name_quality, avatar, explicit_avatar, topic, canonical_alias,
lazy_load_summary, encryption_event, has_member_list, preview_event_rowid, sorting_timestamp, lazy_load_summary, encryption_event, has_member_list, preview_event_rowid, sorting_timestamp,
unread_highlights, unread_notifications, unread_messages, prev_batch unread_highlights, unread_notifications, unread_messages, marked_unread, prev_batch
FROM room FROM room
` `
getRoomsBySortingTimestampQuery = getRoomBaseQuery + `WHERE sorting_timestamp < $1 AND sorting_timestamp > 0 ORDER BY sorting_timestamp DESC LIMIT $2` getRoomsBySortingTimestampQuery = getRoomBaseQuery + `WHERE sorting_timestamp < $1 AND sorting_timestamp > 0 ORDER BY sorting_timestamp DESC LIMIT $2`
@ -50,7 +50,8 @@ const (
unread_highlights = COALESCE($15, room.unread_highlights), unread_highlights = COALESCE($15, room.unread_highlights),
unread_notifications = COALESCE($16, room.unread_notifications), unread_notifications = COALESCE($16, room.unread_notifications),
unread_messages = COALESCE($17, room.unread_messages), unread_messages = COALESCE($17, room.unread_messages),
prev_batch = COALESCE($18, room.prev_batch) marked_unread = COALESCE($18, room.marked_unread),
prev_batch = COALESCE($19, room.prev_batch)
WHERE room_id = $1 WHERE room_id = $1
` `
setRoomPrevBatchQuery = ` setRoomPrevBatchQuery = `
@ -157,6 +158,7 @@ type Room struct {
PreviewEventRowID EventRowID `json:"preview_event_rowid"` PreviewEventRowID EventRowID `json:"preview_event_rowid"`
SortingTimestamp jsontime.UnixMilli `json:"sorting_timestamp"` SortingTimestamp jsontime.UnixMilli `json:"sorting_timestamp"`
UnreadCounts UnreadCounts
MarkedUnread *bool `json:"marked_unread,omitempty"`
PrevBatch string `json:"prev_batch"` PrevBatch string `json:"prev_batch"`
} }
@ -220,6 +222,10 @@ func (r *Room) CheckChangesAndCopyInto(other *Room) (hasChanges bool) {
other.UnreadMessages = r.UnreadMessages other.UnreadMessages = r.UnreadMessages
hasChanges = true hasChanges = true
} }
if r.MarkedUnread != other.MarkedUnread {
other.MarkedUnread = r.MarkedUnread
hasChanges = true
}
if r.PrevBatch != "" && other.PrevBatch == "" { if r.PrevBatch != "" && other.PrevBatch == "" {
other.PrevBatch = r.PrevBatch other.PrevBatch = r.PrevBatch
hasChanges = true hasChanges = true
@ -248,6 +254,7 @@ func (r *Room) Scan(row dbutil.Scannable) (*Room, error) {
&r.UnreadHighlights, &r.UnreadHighlights,
&r.UnreadNotifications, &r.UnreadNotifications,
&r.UnreadMessages, &r.UnreadMessages,
&r.MarkedUnread,
&prevBatch, &prevBatch,
) )
if err != nil { if err != nil {
@ -278,6 +285,7 @@ func (r *Room) sqlVariables() []any {
r.UnreadHighlights, r.UnreadHighlights,
r.UnreadNotifications, r.UnreadNotifications,
r.UnreadMessages, r.UnreadMessages,
r.MarkedUnread,
dbutil.StrPtr(r.PrevBatch), dbutil.StrPtr(r.PrevBatch),
} }
} }

View file

@ -1,4 +1,4 @@
-- v0 -> v6 (compatible with v5+): Latest revision -- v0 -> v7 (compatible with v5+): Latest revision
CREATE TABLE account ( CREATE TABLE account (
user_id TEXT NOT NULL PRIMARY KEY, user_id TEXT NOT NULL PRIMARY KEY,
device_id TEXT NOT NULL, device_id TEXT NOT NULL,
@ -29,6 +29,7 @@ CREATE TABLE room (
unread_highlights INTEGER NOT NULL DEFAULT 0, unread_highlights INTEGER NOT NULL DEFAULT 0,
unread_notifications INTEGER NOT NULL DEFAULT 0, unread_notifications INTEGER NOT NULL DEFAULT 0,
unread_messages INTEGER NOT NULL DEFAULT 0, unread_messages INTEGER NOT NULL DEFAULT 0,
marked_unread INTEGER NOT NULL DEFAULT false,
prev_batch TEXT, prev_batch TEXT,

View file

@ -0,0 +1,2 @@
-- v7 (compatible with v5+): Add room column for marking unread
ALTER TABLE room ADD COLUMN marked_unread INTEGER NOT NULL DEFAULT false;

View file

@ -151,6 +151,12 @@ func (h *HiClient) SendMessage(
} }
func (h *HiClient) MarkRead(ctx context.Context, roomID id.RoomID, eventID id.EventID, receiptType event.ReceiptType) error { func (h *HiClient) MarkRead(ctx context.Context, roomID id.RoomID, eventID id.EventID, receiptType event.ReceiptType) error {
room, err := h.DB.Room.Get(ctx, roomID)
if err != nil {
return fmt.Errorf("failed to get room metadata: %w", err)
} else if room == nil {
return fmt.Errorf("unknown room")
}
content := &mautrix.ReqSetReadMarkers{ content := &mautrix.ReqSetReadMarkers{
FullyRead: eventID, FullyRead: eventID,
} }
@ -161,10 +167,16 @@ func (h *HiClient) MarkRead(ctx context.Context, roomID id.RoomID, eventID id.Ev
} else { } else {
return fmt.Errorf("invalid receipt type: %v", receiptType) return fmt.Errorf("invalid receipt type: %v", receiptType)
} }
err := h.Client.SetReadMarkers(ctx, roomID, content) err = h.Client.SetReadMarkers(ctx, roomID, content)
if err != nil { if err != nil {
return fmt.Errorf("failed to mark event as read: %w", err) return fmt.Errorf("failed to mark event as read: %w", err)
} }
if ptr.Val(room.MarkedUnread) {
err = h.Client.SetRoomAccountData(ctx, roomID, event.AccountDataMarkedUnread.Type, &event.MarkedUnreadEventContent{Unread: false})
if err != nil {
return fmt.Errorf("failed to mark room as read: %w", err)
}
}
return nil return nil
} }

View file

@ -21,6 +21,7 @@ import (
"go.mau.fi/util/emojirunes" "go.mau.fi/util/emojirunes"
"go.mau.fi/util/exzerolog" "go.mau.fi/util/exzerolog"
"go.mau.fi/util/jsontime" "go.mau.fi/util/jsontime"
"go.mau.fi/util/ptr"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/crypto/olm" "maunium.net/go/mautrix/crypto/olm"
@ -772,6 +773,10 @@ func (h *HiClient) processStateAndTimeline(
updatedRoom.Avatar = &dmAvatarURL updatedRoom.Avatar = &dmAvatarURL
} }
} }
mu, ok := accountData[event.AccountDataMarkedUnread]
if ok {
updatedRoom.MarkedUnread = ptr.Ptr(gjson.GetBytes(mu.Content, "unread").Bool())
}
if len(receipts) > 0 { if len(receipts) > 0 {
err = h.DB.Receipt.PutMany(ctx, room.ID, receipts...) err = h.DB.Receipt.PutMany(ctx, room.ID, receipts...)

View file

@ -50,6 +50,7 @@ export interface RoomListEntry {
unread_messages: number unread_messages: number
unread_notifications: number unread_notifications: number
unread_highlights: number unread_highlights: number
marked_unread: boolean
} }
export class StateStore { export class StateStore {
@ -102,6 +103,7 @@ export class StateStore {
entry.meta.unread_messages !== oldEntry.meta.current.unread_messages || entry.meta.unread_messages !== oldEntry.meta.current.unread_messages ||
entry.meta.unread_notifications !== oldEntry.meta.current.unread_notifications || entry.meta.unread_notifications !== oldEntry.meta.current.unread_notifications ||
entry.meta.unread_highlights !== oldEntry.meta.current.unread_highlights || entry.meta.unread_highlights !== oldEntry.meta.current.unread_highlights ||
entry.meta.marked_unread !== oldEntry.meta.current.marked_unread ||
entry.meta.preview_event_rowid !== oldEntry.meta.current.preview_event_rowid || entry.meta.preview_event_rowid !== oldEntry.meta.current.preview_event_rowid ||
entry.events.findIndex(evt => evt.rowid === entry.meta.preview_event_rowid) !== -1 entry.events.findIndex(evt => evt.rowid === entry.meta.preview_event_rowid) !== -1
} }
@ -129,6 +131,7 @@ export class StateStore {
unread_messages: entry.meta.unread_messages, unread_messages: entry.meta.unread_messages,
unread_notifications: entry.meta.unread_notifications, unread_notifications: entry.meta.unread_notifications,
unread_highlights: entry.meta.unread_highlights, unread_highlights: entry.meta.unread_highlights,
marked_unread: entry.meta.marked_unread,
} }
} }

View file

@ -64,6 +64,7 @@ export interface DBRoom {
unread_highlights: number unread_highlights: number
unread_notifications: number unread_notifications: number
unread_messages: number unread_messages: number
marked_unread: boolean
prev_batch: string prev_batch: string
} }

View file

@ -44,6 +44,7 @@
--unread-counter-text-color: var(--inverted-text-color); --unread-counter-text-color: var(--inverted-text-color);
--unread-counter-message-bg: rgba(0, 0, 0, 0.35); --unread-counter-message-bg: rgba(0, 0, 0, 0.35);
--unread-counter-notification-bg: rgba(50, 150, 0, 0.7); --unread-counter-notification-bg: rgba(50, 150, 0, 0.7);
--unread-counter-marked-unread-bg: var(--unread-counter-notification-bg);
--unread-counter-highlight-bg: rgba(200, 0, 0, 0.7); --unread-counter-highlight-bg: rgba(200, 0, 0, 0.7);
--sender-color-0: #a4041d; --sender-color-0: #a4041d;

View file

@ -68,8 +68,9 @@ const EntryInner = ({ room }: InnerProps) => {
<div className="room-name">{room.name}</div> <div className="room-name">{room.name}</div>
{previewText && <div className="message-preview" title={previewText}>{croppedPreviewText}</div>} {previewText && <div className="message-preview" title={previewText}>{croppedPreviewText}</div>}
</div> </div>
{room.unread_messages ? <div className="room-entry-unreads"> {(room.unread_messages || room.marked_unread) ? <div className="room-entry-unreads">
<div className={`unread-count ${ <div className={`unread-count ${
room.marked_unread ? "marked-unread" : ""} ${
room.unread_notifications ? "notified" : ""} ${ room.unread_notifications ? "notified" : ""} ${
room.unread_highlights ? "highlighted" : ""}`} room.unread_highlights ? "highlighted" : ""}`}
> >

View file

@ -117,7 +117,7 @@ div.room-entry {
line-height: 1; line-height: 1;
font-size: .75em; font-size: .75em;
&.notified, &.highlighted { &.notified, &.marked-unread, &.highlighted {
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
padding-bottom: 0; padding-bottom: 0;
@ -125,7 +125,11 @@ div.room-entry {
font-weight: bold; font-weight: bold;
} }
&.notified:not(.highlighted) { &.marked-unread {
background-color: var(--unread-counter-marked-unread-bg);
}
&.notified {
background-color: var(--unread-counter-notification-bg); background-color: var(--unread-counter-notification-bg);
} }

View file

@ -75,10 +75,12 @@ const TimelineView = () => {
&& focused && focused
&& newestEvent && newestEvent
&& newestEvent.timeline_rowid > 0 && newestEvent.timeline_rowid > 0
&& room.readUpToRow < newestEvent.timeline_rowid && (room.meta.current.marked_unread
&& newestEvent.sender !== client.userID || (room.readUpToRow < newestEvent.timeline_rowid
&& newestEvent.sender !== client.userID))
) { ) {
room.readUpToRow = newestEvent.timeline_rowid room.readUpToRow = newestEvent.timeline_rowid
room.meta.current.marked_unread = false
const receiptType = roomCtx.store.preferences.send_read_receipts ? "m.read" : "m.read.private" const receiptType = roomCtx.store.preferences.send_read_receipts ? "m.read" : "m.read.private"
client.rpc.markRead(room.roomID, newestEvent.event_id, receiptType).then( client.rpc.markRead(room.roomID, newestEvent.event_id, receiptType).then(
() => console.log("Marked read up to", newestEvent.event_id, newestEvent.timeline_rowid), () => console.log("Marked read up to", newestEvent.event_id, newestEvent.timeline_rowid),