diff --git a/pkg/hicli/database/room.go b/pkg/hicli/database/room.go index de01edb..cb386b4 100644 --- a/pkg/hicli/database/room.go +++ b/pkg/hicli/database/room.go @@ -23,7 +23,7 @@ const ( getRoomBaseQuery = ` 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, - unread_highlights, unread_notifications, unread_messages, prev_batch + unread_highlights, unread_notifications, unread_messages, marked_unread, prev_batch FROM room ` 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_notifications = COALESCE($16, room.unread_notifications), 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 ` setRoomPrevBatchQuery = ` @@ -157,6 +158,7 @@ type Room struct { PreviewEventRowID EventRowID `json:"preview_event_rowid"` SortingTimestamp jsontime.UnixMilli `json:"sorting_timestamp"` UnreadCounts + MarkedUnread *bool `json:"marked_unread,omitempty"` PrevBatch string `json:"prev_batch"` } @@ -220,6 +222,10 @@ func (r *Room) CheckChangesAndCopyInto(other *Room) (hasChanges bool) { other.UnreadMessages = r.UnreadMessages hasChanges = true } + if r.MarkedUnread != other.MarkedUnread { + other.MarkedUnread = r.MarkedUnread + hasChanges = true + } if r.PrevBatch != "" && other.PrevBatch == "" { other.PrevBatch = r.PrevBatch hasChanges = true @@ -248,6 +254,7 @@ func (r *Room) Scan(row dbutil.Scannable) (*Room, error) { &r.UnreadHighlights, &r.UnreadNotifications, &r.UnreadMessages, + &r.MarkedUnread, &prevBatch, ) if err != nil { @@ -278,6 +285,7 @@ func (r *Room) sqlVariables() []any { r.UnreadHighlights, r.UnreadNotifications, r.UnreadMessages, + r.MarkedUnread, dbutil.StrPtr(r.PrevBatch), } } diff --git a/pkg/hicli/database/upgrades/00-latest-revision.sql b/pkg/hicli/database/upgrades/00-latest-revision.sql index 923445c..026c77b 100644 --- a/pkg/hicli/database/upgrades/00-latest-revision.sql +++ b/pkg/hicli/database/upgrades/00-latest-revision.sql @@ -1,4 +1,4 @@ --- v0 -> v6 (compatible with v5+): Latest revision +-- v0 -> v7 (compatible with v5+): Latest revision CREATE TABLE account ( user_id TEXT NOT NULL PRIMARY KEY, device_id TEXT NOT NULL, @@ -29,6 +29,7 @@ CREATE TABLE room ( unread_highlights INTEGER NOT NULL DEFAULT 0, unread_notifications INTEGER NOT NULL DEFAULT 0, unread_messages INTEGER NOT NULL DEFAULT 0, + marked_unread INTEGER NOT NULL DEFAULT false, prev_batch TEXT, diff --git a/pkg/hicli/database/upgrades/07-marked-unread.sql b/pkg/hicli/database/upgrades/07-marked-unread.sql new file mode 100644 index 0000000..ddad882 --- /dev/null +++ b/pkg/hicli/database/upgrades/07-marked-unread.sql @@ -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; diff --git a/pkg/hicli/send.go b/pkg/hicli/send.go index ce30a8e..1f403dc 100644 --- a/pkg/hicli/send.go +++ b/pkg/hicli/send.go @@ -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 { + 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{ FullyRead: eventID, } @@ -161,10 +167,16 @@ func (h *HiClient) MarkRead(ctx context.Context, roomID id.RoomID, eventID id.Ev } else { 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 { 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 } diff --git a/pkg/hicli/sync.go b/pkg/hicli/sync.go index 8c566c9..c16d312 100644 --- a/pkg/hicli/sync.go +++ b/pkg/hicli/sync.go @@ -21,6 +21,7 @@ import ( "go.mau.fi/util/emojirunes" "go.mau.fi/util/exzerolog" "go.mau.fi/util/jsontime" + "go.mau.fi/util/ptr" "maunium.net/go/mautrix" "maunium.net/go/mautrix/crypto" "maunium.net/go/mautrix/crypto/olm" @@ -772,6 +773,10 @@ func (h *HiClient) processStateAndTimeline( updatedRoom.Avatar = &dmAvatarURL } } + mu, ok := accountData[event.AccountDataMarkedUnread] + if ok { + updatedRoom.MarkedUnread = ptr.Ptr(gjson.GetBytes(mu.Content, "unread").Bool()) + } if len(receipts) > 0 { err = h.DB.Receipt.PutMany(ctx, room.ID, receipts...) diff --git a/web/src/api/statestore/main.ts b/web/src/api/statestore/main.ts index 6b1d22b..d03dde2 100644 --- a/web/src/api/statestore/main.ts +++ b/web/src/api/statestore/main.ts @@ -50,6 +50,7 @@ export interface RoomListEntry { unread_messages: number unread_notifications: number unread_highlights: number + marked_unread: boolean } export class StateStore { @@ -102,6 +103,7 @@ export class StateStore { entry.meta.unread_messages !== oldEntry.meta.current.unread_messages || entry.meta.unread_notifications !== oldEntry.meta.current.unread_notifications || 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.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_notifications: entry.meta.unread_notifications, unread_highlights: entry.meta.unread_highlights, + marked_unread: entry.meta.marked_unread, } } diff --git a/web/src/api/types/hitypes.ts b/web/src/api/types/hitypes.ts index edd0c64..3b65e9a 100644 --- a/web/src/api/types/hitypes.ts +++ b/web/src/api/types/hitypes.ts @@ -64,6 +64,7 @@ export interface DBRoom { unread_highlights: number unread_notifications: number unread_messages: number + marked_unread: boolean prev_batch: string } diff --git a/web/src/index.css b/web/src/index.css index faf450b..4d88270 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -44,6 +44,7 @@ --unread-counter-text-color: var(--inverted-text-color); --unread-counter-message-bg: rgba(0, 0, 0, 0.35); --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); --sender-color-0: #a4041d; diff --git a/web/src/ui/roomlist/Entry.tsx b/web/src/ui/roomlist/Entry.tsx index 5b6346d..1301620 100644 --- a/web/src/ui/roomlist/Entry.tsx +++ b/web/src/ui/roomlist/Entry.tsx @@ -68,8 +68,9 @@ const EntryInner = ({ room }: InnerProps) => {