╭ changed ──────────────────╮ │src/ui/Repo/Discussions.zig│ ╰───────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────╮ │ 8 const Comment = @import("Comment.zig"); │ │ 9 const Attachment = @import("Attachment.zig"); │ │10 const thread = ui.widget.thread; │ │11 +const srch_thrd = @import("../../search_thread.zig");│ │12 │ │13 const wasm = builtin.target.cpu.arch == .wasm32; │ │14 │ ╰─────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │47 }; │ │48 │ │49 identity: []const u8, │ │49 -label: []const u8, │ │50 // the decoded query the list is filtered to, or null when unfiltered.│ │51 search: ?[]const u8 = null, │ │52 selected_id: []const u8, │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │69 pub const thread_name = "discussion"; │ │70 pub const header_widget_name = "repo_discussions_header"; │ │71 │ │72 -pub fn listRoute(identity: []const u8, _: Status, label: []const u8, selected: []const u8) ?ui.RoutablePage {│ │73 - return ui.RoutablePage.repoDiscussionsRoute(identity, label, selected); │ │72 +pub fn listRoute(identity: []const u8, _: Status, selected: []const u8) ?ui.RoutablePage { │ │73 + return ui.RoutablePage.repoDiscussionsRoute(identity, selected); │ │74 } │ │75 │ │76 pub fn selectedThread(self: *const Self) ?*const DiscussionWithId { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────╮ │88 pub fn emptyResult( │ │89 aa: std.mem.Allocator, │ │90 identity: []const u8, │ │91 - label: []const u8, │ │91 search: []const u8, │ │92 selected_id: []const u8,│ │93 comment_id: []const u8, │ ╰────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 96 ) !Self { │ │ 97 return .{ │ │ 98 .identity = try aa.dupe(u8, identity), │ │100 - .label = try aa.dupe(u8, label), │ │ 99 .search = if (search.len == 0) null else std.Uri.percentDecodeInPlace(try aa.dupe(u8, search)),│ │100 .selected_id = try aa.dupe(u8, selected_id), │ │101 .comment_id = try aa.dupe(u8, comment_id), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │115 io: std.Io, │ │116 admin_moment: ?evt.AdminDB.HashMap(.read_only),│ │117 identity: []const u8, │ │120 - label: []const u8, │ │118 search: []const u8, │ │119 selected_id: []const u8, │ │120 comment_id: []const u8, │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │122 view: ui.RoutablePage.DiscussionsView, │ │123 viewer: ?ui.Viewer, │ │124 ) !Self { │ │128 - var empty = try emptyResult(arena.allocator(), identity, label, search, selected_id, comment_id, comments_start, view);│ │125 + var empty = try emptyResult(arena.allocator(), identity, search, selected_id, comment_id, comments_start, view); │ │126 // the early returns below still let the viewer write │ │127 empty.viewer = viewer; │ │128 const aa = arena.allocator(); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │129 const DB = evt.EventDB(repo_opts.hash); │ │130 const rooted = empty.selected_id.len != 0; │ │134 - const labeled = empty.label.len != 0; │ │135 - const strict = rooted or labeled; │ │131 │ │132 const gpa = arena.child_allocator; │ │133 var event_db_maybe: ?evt.LocalEventDB(repo_opts.hash) = if (repo_kind == .git) try evt.LocalEventDB(repo_opts.hash).openReadOnly(io, gpa, repo.core.repo_dir) else null;│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────╮ │138 return empty │ │139 else │ │140 evt.currentMoment(repo_opts, repo)) catch {│ │146 - if (strict) return error.NotFound; │ │141 + if (rooted) return error.NotFound; │ │142 return empty; │ │143 }; │ │144 │ ╰────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │150 - const set_maybe: ?DB.SortedSet(.read_only) = if (labeled) blk: { │ │151 - const labels_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.label_to_id_set_key)) orelse return error.NotFound;│ │152 - const label_sets = try DB.SortedMap(.read_only).init(labels_cursor); │ │153 - const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, empty.label)); │ │154 - const cursor = try label_sets.getCursor(decoded) orelse return error.NotFound; │ │155 - break :blk try DB.SortedSet(.read_only).init(cursor); │ │156 - } else if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.active_id_set_key))) |cursor| │ │157 - try DB.SortedSet(.read_only).init(cursor) │ │158 - else │ │159 - null; │ │145 + // the listing intersects every label's set, or is the active set without │ │146 + // labels, since each label's set lies within it. discussions have no │ │147 + // assignees, so an assignee term lists nothing. │ │148 + const filters = try srch_thrd.parseFilters(aa, empty.search orelse ""); │ │149 + const sets: []const DB.SortedSet(.read_only) = blk: { │ │150 + if (filters.assignees.len != 0) break :blk &.{}; │ │151 + var list: std.ArrayList(DB.SortedSet(.read_only)) = .empty; │ │152 + if (filters.labels.len == 0) { │ │153 + const active_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.active_id_set_key)) orelse break :blk &.{}; │ │154 + try list.append(aa, try DB.SortedSet(.read_only).init(active_cursor)); │ │155 + } else { │ │156 + const labels_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.label_to_id_set_key)) orelse break :blk &.{}; │ │157 + const label_sets = try DB.SortedMap(.read_only).init(labels_cursor); │ │158 + for (filters.labels) |label| { │ │159 + const cursor = try label_sets.getCursor(label) orelse break :blk &.{}; │ │160 + try list.append(aa, try DB.SortedSet(.read_only).init(cursor)); │ │161 + } │ │162 + } │ │163 + break :blk list.items; │ │164 + }; │ │165 │ │166 const records_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Discussion.record_map_key)) orelse { │ │162 - if (strict) return error.NotFound; │ │167 + if (rooted) return error.NotFound; │ │168 return empty; │ │169 }; │ │170 const records = try DB.HashMap(.read_only).init(records_cursor); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯