╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────────╮ │src/ui/Repo/Discussions.zig│ ╰───────────────────────────╯ ╭────────────────────────────────╮ │82 aa: std.mem.Allocator, │ │83 identity: []const u8, │ │84 tag: []const u8, │ │85 + search: []const u8, │ │86 selected_id: []const u8,│ │87 comment_id: []const u8, │ │88 comments_start: usize, │ ╰────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │91 return .{ │ │92 .identity = try aa.dupe(u8, identity), │ │93 .tag = try aa.dupe(u8, tag), │ │94 + .search = if (search.len == 0) null else std.Uri.percentDecodeInPlace(try aa.dupe(u8, search)),│ │95 .selected_id = try aa.dupe(u8, selected_id), │ │96 .comment_id = try aa.dupe(u8, comment_id), │ │97 .comments_start = comments_start, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │111 admin_moment: ?evt.AdminDB.HashMap(.read_only),│ │112 identity: []const u8, │ │113 tag: []const u8, │ │114 + search: []const u8, │ │115 selected_id: []const u8, │ │116 comment_id: []const u8, │ │117 comments_start: usize, │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │118 view: ui.RoutablePage.DiscussionsView, │ │119 ) !Self { │ │113 - const empty = try emptyResult(arena.allocator(), identity, tag, selected_id, comment_id, comments_start, view); │ │120 + const empty = try emptyResult(arena.allocator(), identity, tag, search, selected_id, comment_id, comments_start, view);│ │121 const aa = arena.allocator(); │ │122 const DB = evt.EventDB(repo_opts.hash); │ │123 const rooted = empty.selected_id.len != 0; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │168 } │ │169 │ │170 const discussion_comments_start = if (empty.comment_id.len == 0) comments_start else 0; │ │164 - const loaded_window = try loadWindow(repo_opts.hash, arena, admin_moment, haxy_moment, records, set_maybe, root_key, empty.selected_id, discussion_comments_start); │ │171 + const loaded_window = try thread.loadWindow(Self, .discuss, repo_opts.hash, arena, admin_moment, haxy_moment, records, set_maybe, root_key, null, empty.selected_id, discussion_comments_start, empty.search);│ │172 const comment_page = if (empty.comment_id.len == 0) │ │173 null │ │174 else │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │189 return .{ │ │190 .identity = empty.identity, │ │191 .tag = empty.tag, │ │192 + .search = empty.search, │ │193 .selected_id = empty.selected_id,│ │194 .comment_id = empty.comment_id, │ │195 .comments_start = comments_start,│ ╰──────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │201 }; │ │202 } │ │203 │ │196 -fn loadWindow( │ │197 - comptime hash_kind: hash.HashKind, │ │198 - arena: *std.heap.ArenaAllocator, │ │199 - admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │200 - haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), │ │201 - records: evt.EventDB(hash_kind).HashMap(.read_only), │ │202 - set_maybe: ?evt.EventDB(hash_kind).SortedSet(.read_only), │ │203 - root_key: ?[]const u8, │ │204 - selected_id: []const u8, │ │205 - comments_start: usize, │ │206 -) !Window { │ │207 - const set = set_maybe orelse return .empty; │ │208 - const DB = evt.EventDB(hash_kind); │ │209 - const aa = arena.allocator(); │ │210 - │ │211 - var prev_id: ?[]const u8 = null; │ │212 - var iter = if (root_key) |key| blk: { │ │213 - const rank = try set.rank(key); │ │214 - if (rank > 0 and rank <= page_size) { │ │215 - prev_id = ""; │ │216 - } else if (rank > page_size) { │ │217 - const pair = try set.getIndexKeyValuePair(@intCast(rank - page_size)) orelse return error.NotFound; │ │218 - var order_key: [@sizeOf(u64) + evt.event_id_size]u8 = undefined; │ │219 - _ = try pair.key_cursor.readBytes(&order_key); │ │220 - const hex = std.fmt.bytesToHex(order_key[@sizeOf(u64)..].*, .lower); │ │221 - prev_id = try aa.dupe(u8, &hex); │ │222 - } │ │223 - break :blk try set.iteratorFrom(key); │ │224 - } else try set.iteratorFromIndex(0); │ │225 - │ │226 - var discussions: std.ArrayList(DiscussionWithId) = .empty; │ │227 - var next_id: ?[]const u8 = null; │ │228 - while (try iter.next()) |cursor| { │ │229 - const id = try evt.readOrderKeyId(DB, cursor); │ │230 - const id_hex = std.fmt.bytesToHex(id, .lower); │ │231 - if (discussions.items.len == page_size) { │ │232 - next_id = try aa.dupe(u8, &id_hex); │ │233 - break; │ │234 - } │ │235 - const record_cursor = try records.getCursor(hash.hashInt(hash_kind, &id)) orelse continue; │ │236 - const record = try evt.read(evt.Discussion.Record, DB, hash_kind, arena, try DB.HashMap(.read_only).init(record_cursor));│ │237 - try discussions.append(aa, .{ │ │238 - .id = try aa.dupe(u8, &id_hex), │ │239 - .record = record, │ │240 - .author = try ui.Author.initFromEmail(admin_moment, arena, record.author_email), │ │241 - .comments = try Comment.loadWindow( │ │242 - hash_kind, │ │243 - arena, │ │244 - admin_moment, │ │245 - haxy_moment, │ │246 - evt.Comment.thread_id_to_comment_id_set_key, │ │247 - &id_hex, │ │248 - if (std.mem.eql(u8, &id_hex, selected_id)) comments_start else 0, │ │249 - ), │ │250 - .attachments = try Attachment.load(hash_kind, arena, haxy_moment, &id_hex), │ │251 - }); │ │252 - } │ │253 - │ │254 - return .{ │ │255 - .items = discussions.items, │ │256 - .prev_id = prev_id, │ │257 - .next_id = next_id, │ │258 - .count = @intCast(try set.count()), │ │259 - }; │ │260 -} │ │204 pub const View = thread.View(.discuss, Self); │ │205 pub const Detail = thread.Detail(.discuss, Self); │ │206 pub const detail_widget_name = "repo_discussion_detail"; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │216 │ │217 // tabs switching between the discussions page's views │ │218 pub fn initHeader(allocator: std.mem.Allocator, session: *ui.Session, data: *const Self) !Header {│ │276 - var header = try Header.init(allocator); │ │219 + var header = try Header.init(allocator, session, data.search); │ │220 errdefer header.deinit(allocator); │ │221 const aa = session.page_arena.allocator(); │ │222 const selected_index = View.viewIndex(data.view); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │224 │ │225 // recent discussions │ │226 { │ │284 - const route = ui.RoutablePage.repoDiscussionsRoute(data.identity, data.tag, "") orelse return error.RouteTooLong; │ │227 + const route = try thread.searchRoute(ui.RoutablePage.repoDiscussionsRoute(data.identity, data.tag, ""), data.search);│ │228 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == 0); │ │229 var label_buf: [64]u8 = undefined; │ │287 - const label = try std.fmt.bufPrint(&label_buf, recent_tab_label ++ " ({d})", .{data.recent.count}); │ │230 + const label = try thread.countLabel(&label_buf, recent_tab_label, data.recent); │ │231 try header.addTab(allocator, label, link, 0); │ │232 } │ │233 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │234 // tags tab, labeled with the active tag filter │ │235 { │ │293 - const tags_route = ui.RoutablePage.repoThreadTagsRoute(.discuss, data.identity, data.tag) orelse return error.RouteTooLong; │ │236 + const tags_route = try thread.searchRoute(ui.RoutablePage.repoThreadTagsRoute(.discuss, data.identity, data.tag), data.search);│ │237 const tags_link = try ui.inPageTabLink(session, tags_route, page_selected and selected_index == View.viewIndex(.tags)); │ │238 const label = if (data.tag.len == 0) tags_tab_label else blk: { │ │239 const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, data.tag)); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯