╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │153 name: Array(repo_route_max_len), │ │154 // the label the list is filtered to, url-encoded ("" = unfiltered). │ │155 label: Array(issue_label_route_max_len) = .{}, │ │156 + // the assignee the list is filtered to, a url-encoded name or email as│ │157 + // typed ("" = unfiltered). │ │158 + assignee: Array(assignee_route_max_len) = .{}, │ │159 // the query the list is filtered to, url-encoded ("" = unfiltered). │ │160 search: Array(search_route_max_len) = .{}, │ │161 // the issue the route names: the list's window root, or the issue the │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │227 │ │228 // the "key:value" segments of a url tail, collected in any order │ │229 const Params = struct { │ │227 - const ParamKey = enum { start, line, branch, tag, label, object, base, patchrev, theirs, find, search, from, moment, merge, user }; │ │230 + const ParamKey = enum { start, line, branch, tag, label, assignee, object, base, patchrev, theirs, find, search, from, moment, merge, user };│ │231 │ │232 // a branch:/tag:/object: param; the value stays url-encoded │ │233 const RefParam = struct { kind: RefOrOid, value: []const u8 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │313 const message_seg = "message"; │ │314 // the `Params` key spellings the url emitters use │ │315 const label_filter_seg = @tagName(Params.ParamKey.label) ++ ":";│ │316 + const assignee_seg = @tagName(Params.ParamKey.assignee) ++ ":"; │ │317 const start_seg = @tagName(Params.ParamKey.start) ++ ":"; │ │318 const line_seg = @tagName(Params.ParamKey.line) ++ ":"; │ │319 const find_seg = @tagName(Params.ParamKey.find) ++ ":"; │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │348 pub const issue_label_route_max_len = evt.Issue.label_max_len * 3; │ │349 pub const patch_label_route_max_len = evt.Patch.label_max_len * 3; │ │350 pub const discussion_label_route_max_len = evt.Discussion.label_max_len * 3;│ │351 + │ │352 + // a url-encoded email can grow to three bytes per source byte. │ │353 + pub const assignee_route_max_len = evt.Assignment.email_max_len * 3; │ │354 │ │355 // caps the resolve view's theirs: field list (a longer one doesn't route). │ │356 pub const theirs_route_max_len = 512; │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │655 return withEncodedSearch(self, search.slice()); │ │656 } │ │657 │ │651 - // the label and search segments of a thread list url, either one optional │ │652 - fn writeListFilters(writer: *std.Io.Writer, label: []const u8, search: []const u8) !void { │ │658 + // the label, assignee and search segments of a thread list url, each optional │ │659 + fn writeListFilters(writer: *std.Io.Writer, label: []const u8, assignee: []const u8, search: []const u8) !void {│ │660 if (label.len != 0) try writer.print("/" ++ label_filter_seg ++ "{s}", .{label}); │ │661 + if (assignee.len != 0) try writer.print("/" ++ assignee_seg ++ "{s}", .{assignee}); │ │662 if (search.len != 0) try writer.print("/" ++ search_seg ++ "{s}", .{search}); │ │663 } │ │664 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │672 return route; │ │673 } │ │674 │ │675 + // carry an already-encoded assignee filter on an issues route │ │676 + pub fn withEncodedAssignee(self: ?RoutablePage, value: []const u8) ?RoutablePage { │ │677 + var route = self orelse return null; │ │678 + switch (route) { │ │679 + .repo_issues => |*i| i.assignee = Array(assignee_route_max_len).from(value) orelse return null,│ │680 + else => return null, │ │681 + } │ │682 + return route; │ │683 + } │ │684 + │ │685 // carry an already-encoded search param, as a url hands it over │ │686 fn withEncodedSearch(self: ?RoutablePage, value: []const u8) ?RoutablePage { │ │687 var route = self orelse return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1174 // and reloading keep the label and the query │ │1175 if (i.selected.len != 0) { │ │1176 try out.writer.print("{s}/" ++ issue_seg ++ "{s}", .{ prefix, i.selected.slice() }); │ │1159 - try writeListFilters(&out.writer, i.label.slice(), i.search.slice()); │ │1177 + try writeListFilters(&out.writer, i.label.slice(), i.assignee.slice(), i.search.slice()); │ │1178 if (i.comments_start != 0) try out.writer.print("/" ++ start_seg ++ "{d}", .{i.comments_start});│ │1179 break :blk out.written(); │ │1180 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1181 try out.writer.print("{s}/issues/{s}", .{ prefix, @tagName(i.view) }); │ │1164 - try writeListFilters(&out.writer, i.label.slice(), i.search.slice()); │ │1182 + try writeListFilters(&out.writer, i.label.slice(), i.assignee.slice(), i.search.slice());│ │1183 break :blk out.written(); │ │1184 }, │ │1185 .repo_patches => |p| blk: { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1214 // and reloading keep the label and the query │ │1215 if (p.selected.len != 0) { │ │1216 try out.writer.print("{s}/" ++ patch_seg ++ "{s}", .{ prefix, p.selected.slice() }); │ │1199 - try writeListFilters(&out.writer, p.label.slice(), p.search.slice()); │ │1217 + try writeListFilters(&out.writer, p.label.slice(), "", p.search.slice()); │ │1218 if (p.comments_start != 0) try out.writer.print("/" ++ start_seg ++ "{d}", .{p.comments_start});│ │1219 break :blk out.written(); │ │1220 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │1221 try out.writer.print("{s}/patches/{s}", .{ prefix, @tagName(p.view) }); │ │1204 - try writeListFilters(&out.writer, p.label.slice(), p.search.slice()); │ │1222 + try writeListFilters(&out.writer, p.label.slice(), "", p.search.slice());│ │1223 break :blk out.written(); │ │1224 }, │ │1225 .repo_discussions => |t| blk: { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯