╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 91 _ = std.fmt.hexToBytes(&id, selected) catch return error.NotFound; │ │ 92 if (!std.mem.eql(u8, selected, &std.fmt.bytesToHex(id, .lower))) return error.NotFound; │ │ 93 const kind = selected_kind orelse return error.NotFound; │ │ 99 - const stored_kind = (try readKind(repo_opts.hash, kind_map, &id)) orelse return error.NotFound; │ │ 94 + const stored_kind = (try evt.readEventKind(repo_opts.hash, kind_map, &id)) orelse return error.NotFound; │ │ 95 if (stored_kind != kind) return error.NotFound; │ │101 - const created_ts = try readCreatedTs(repo_opts.hash, arena, haxy_moment, kind, &id); │ │102 - const key = evt.orderKeyDesc(created_ts, &id); │ │103 - if (!try id_set.contains(&key)) return error.NotFound; │ │104 - root_key = key; │ │ 96 + const meta = try readMeta(repo_opts.hash, arena, haxy_moment, kind, &id); │ │ 97 + result.view = if (meta.removed) .removed else .active; │ │ 98 + root_key = evt.orderKeyDesc(meta.created_ts, &id); │ │ 99 } │ │106 - var iter = if (root_key) |*key| try id_set.iteratorFrom(key) else try id_set.iteratorFromIndex(0); │ │100 + │ │101 + for ([_]ui.RoutablePage.EventsView{ .active, .removed }) |view| { │ │102 + const id_set_cursor = switch (view) { │ │103 + .active => active_cursor, │ │104 + .removed => removed_cursor, │ │105 + }; │ │106 + const event_window = switch (view) { │ │107 + .active => &result.active, │ │108 + .removed => &result.removed, │ │109 + }; │ │110 + const id_set = try DB.SortedSet(.read_only).init(id_set_cursor orelse { │ │111 + if (require_selected_event and result.view == view) return error.NotFound; │ │112 + continue; │ │113 + }); │ │114 + event_window.count = @intCast(try id_set.count()); │ │115 + const selected_key = if (result.view == view) root_key else null; │ │116 + if (selected_key) |*key| if (!try id_set.contains(key)) return error.NotFound; │ │117 │ │108 - var events: std.ArrayList(Item) = .empty; │ │109 - while (try iter.next()) |cursor_value| { │ │110 - const id = try evt.readOrderKeyId(DB, cursor_value); │ │111 - const kind = (try readKind(repo_opts.hash, kind_map, &id)) orelse continue; │ │112 - if (events.items.len == page_size) { │ │113 - result.next = .{ .id = try formatId(aa, &id), .kind = kind }; │ │114 - break; │ │118 + var iter = if (selected_key) |*key| try id_set.iteratorFrom(key) else try id_set.iteratorFromIndex(0); │ │119 + var events: std.ArrayList(Item) = .empty; │ │120 + while (try iter.next()) |cursor_value| { │ │121 + const id = try evt.readOrderKeyId(DB, cursor_value); │ │122 + const kind = (try evt.readEventKind(repo_opts.hash, kind_map, &id)) orelse continue; │ │123 + if (events.items.len == page_size) { │ │124 + event_window.next = .{ .id = try formatId(aa, &id), .kind = kind }; │ │125 + break; │ │126 + } │ │127 + const item = (try readItem(repo_opts.hash, arena, haxy_moment, kind_map, admin_moment, identity, view == .active, kind, &id)) orelse continue;│ │128 + try events.append(aa, item); │ │129 } │ │116 - const item = (try readItem(repo_opts.hash, arena, haxy_moment, kind_map, admin_moment, identity, kind, &id)) orelse continue; │ │117 - try events.append(aa, item); │ │130 + event_window.events = events.items; │ │131 } │ │119 - result.events = events.items; │ │132 return result; │ │121 -} │ │122 - │ │123 -fn readKind( │ │124 - comptime hash_kind: hash.HashKind, │ │125 - kind_map: evt.EventDB(hash_kind).HashMap(.read_only), │ │126 - id: *const [evt.event_id_size]u8, │ │127 -) !?evt.EventKind { │ │128 - const cursor = try kind_map.getCursor(hash.hashInt(hash_kind, id)) orelse return null; │ │129 - var buffer: [64]u8 = undefined; │ │130 - return std.meta.stringToEnum(evt.EventKind, try cursor.readBytes(&buffer)) orelse error.InvalidEventKind; │ │133 } │ │134 │ │133 -pub fn empty(aa: std.mem.Allocator, identity: []const u8, local: bool, failure: ?[]const u8) !Self { │ │135 +pub fn empty(aa: std.mem.Allocator, identity: []const u8, view: ui.RoutablePage.EventsView, local: bool, failure: ?[]const u8) !Self { │ │136 return .{ │ │137 .identity = try aa.dupe(u8, identity), │ │136 - .events = &.{}, │ │137 - .next = null, │ │138 - .header = if (local) try (try Header.empty(aa, failure)).withCount(aa, 0) else try Header.remote(aa, 0), │ │138 + .view = view, │ │139 + .active = .{}, │ │140 + .removed = .{}, │ │141 + .header = if (local) try Header.empty(aa, failure) else Header.remote(), │ │142 + }; │ │143 +} │ │144 + │ │145 +fn window(self: *const Self, view: ui.RoutablePage.EventsView) *const Window { │ │146 + return switch (view) { │ │147 + .active => &self.active, │ │148 + .removed => &self.removed, │ │149 }; │ │150 } │ │151 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │169 return try evt.read(T.Record, DB, hash_kind, arena, record);│ │170 } │ │171 │ │162 -fn readCreatedTs( │ │172 +const EventMeta = struct { │ │173 + created_ts: u64, │ │174 + removed: bool, │ │175 +}; │ │176 + │ │177 +fn readMeta( │ │178 comptime hash_kind: hash.HashKind, │ │179 arena: *std.heap.ArenaAllocator, │ │180 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), │ ╰─────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │181 kind: evt.EventKind, │ │182 id: *const [evt.event_id_size]u8, │ │168 -) !u64 { │ │183 +) !EventMeta { │ │184 return switch (kind) { │ │170 - .user => (try readRecord(evt.User, hash_kind, arena, haxy_moment, id) orelse return error.NotFound).created_ts, │ │171 - .repo => (try readRecord(evt.Repo, hash_kind, arena, haxy_moment, id) orelse return error.NotFound).created_ts, │ │172 - .issue => (try readRecord(evt.Issue, hash_kind, arena, haxy_moment, id) orelse return error.NotFound).created_ts, │ │173 - .comment => (try readRecord(evt.Comment, hash_kind, arena, haxy_moment, id) orelse return error.NotFound).created_ts, │ │174 - .attach => (try readRecord(evt.Attachment, hash_kind, arena, haxy_moment, id) orelse return error.NotFound).created_ts,│ │185 + inline else => |event_kind| blk: { │ │186 + const T = switch (event_kind) { │ │187 + .user => evt.User, │ │188 + .repo => evt.Repo, │ │189 + .issue => evt.Issue, │ │190 + .comment => evt.Comment, │ │191 + .attach => evt.Attachment, │ │192 + }; │ │193 + const record = (try readRecord(T, hash_kind, arena, haxy_moment, id)) orelse return error.NotFound; │ │194 + break :blk .{ .created_ts = record.created_ts, .removed = record.removed }; │ │195 + }, │ │196 }; │ │197 } │ │198 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────╮ │203 kind_map: evt.EventDB(hash_kind).HashMap(.read_only),│ │204 admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │205 identity: []const u8, │ │206 + include_view_url: bool, │ │207 kind: evt.EventKind, │ │208 id: *const [evt.event_id_size]u8, │ │209 ) !?Item { │ ╰──────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────╮ │210 var item: Item = .{ │ │211 .id = try formatId(arena.allocator(), id),│ │212 .kind = kind, │ │191 - .deleted = false, │ │213 .author = .unknown, │ │214 }; │ │215 switch (kind) { │ ╰───────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │216 .user => { │ │217 const record = (try readRecord(evt.User, hash_kind, arena, haxy_moment, id)) orelse return null;│ │197 - item.deleted = record.deleted; │ │218 item.author = try ui.Author.initFromEmail(admin_moment, arena, record.event.email); │ │219 }, │ │220 .repo => { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │221 const record = (try readRecord(evt.Repo, hash_kind, arena, haxy_moment, id)) orelse return null; │ │202 - item.deleted = record.deleted; │ │222 if (admin_moment) |moment| { │ │223 if (try evt.User.readById(evt.AdminDB, evt.admin_repo_opts.hash, moment, arena, record.event.user_id)) |user|│ │224 item.author = .{ .user_name = user.event.name }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │226 }, │ │227 .issue => { │ │228 const record = (try readRecord(evt.Issue, hash_kind, arena, haxy_moment, id)) orelse return null; │ │210 - item.deleted = record.deleted; │ │229 item.author = try ui.Author.initFromEmail(admin_moment, arena, record.author_email); │ │230 + if (!include_view_url) return item; │ │231 const route = ui.RoutablePage.repoIssueCommentsRoute(identity, item.id, 0) orelse return error.RouteTooLong;│ │232 item.view_url = try route.toUrl(arena); │ │233 }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │234 .comment => { │ │235 const record = (try readRecord(evt.Comment, hash_kind, arena, haxy_moment, id)) orelse return null;│ │217 - item.deleted = record.deleted; │ │236 item.author = try ui.Author.initFromEmail(admin_moment, arena, record.author_email); │ │237 + if (!include_view_url) return item; │ │238 var thread_id: [evt.event_id_size]u8 = undefined; │ │239 _ = std.fmt.hexToBytes(&thread_id, &record.event.thread_id) catch return error.InvalidEventId; │ │221 - const thread_kind = (try readKind(hash_kind, kind_map, &thread_id)) orelse return item; │ │240 + const thread_kind = (try evt.readEventKind(hash_kind, kind_map, &thread_id)) orelse return item; │ │241 const route = switch (thread_kind) { │ │242 .issue => ui.RoutablePage.repoCommentsRoute(identity, &record.event.thread_id, item.id, 0), │ │243 else => null, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │246 }, │ │247 .attach => { │ │248 const record = (try readRecord(evt.Attachment, hash_kind, arena, haxy_moment, id)) orelse return null;│ │230 - item.deleted = record.deleted; │ │249 item.author = try ui.Author.initFromEmail(admin_moment, arena, record.author_email); │ │250 + if (!include_view_url) return item; │ │251 var parent_id: [evt.event_id_size]u8 = undefined; │ │252 _ = std.fmt.hexToBytes(&parent_id, &record.event.parent_id) catch return error.InvalidEventId; │ │234 - const parent_kind = (try readKind(hash_kind, kind_map, &parent_id)) orelse return item; │ │253 + const parent_kind = (try evt.readEventKind(hash_kind, kind_map, &parent_id)) orelse return item; │ │254 const route = switch (parent_kind) { │ │255 .issue => ui.RoutablePage.repoIssueCommentsRoute(identity, &record.event.parent_id, 0), │ │256 else => null, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯