╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │275 .patch => { │ │276 const record = (try readRecord(evt.Patch, hash_kind, arena, haxy_moment, id)) orelse return null; │ │277 item.author = try ui.Author.initFromEmail(admin_moment, arena, record.author_email); │ │278 + if (!include_view_url) return item; │ │279 + const route = ui.RoutablePage.repoThreadCommentsRoute(.patch, identity, item.id, 0) orelse return error.RouteTooLong;│ │280 + item.view_url = try route.toUrl(arena); │ │281 }, │ │282 .patchrev => { │ │283 const record = (try readRecord(evt.PatchRev, hash_kind, arena, haxy_moment, id)) orelse return null; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │26 // the issues tab's tag filter, url-encoded ("" = unfiltered), so the tab links│ │27 // back to the filtered list. │ │28 issues_tag: []const u8, │ │29 +patches_tag: []const u8, │ │30 discussions_tag: []const u8, │ │31 │ │32 const Self = @This(); │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │33 │ │33 -pub fn init(arena: *std.heap.ArenaAllocator, name: []const u8, owner_name: []const u8, ref_or_oid: RefOrOid, ref_or_oid_value: []const u8, issues_tag: []const u8, discussions_tag: []const u8) !Self { │ │34 +pub fn init(arena: *std.heap.ArenaAllocator, name: []const u8, owner_name: []const u8, ref_or_oid: RefOrOid, ref_or_oid_value: []const u8, issues_tag: []const u8, patches_tag: []const u8, discussions_tag: []const u8) !Self {│ │35 return .{ │ │36 .name = name, │ │37 .owner_name = owner_name, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────╮ │40 .ref_or_oid = ref_or_oid, │ │41 .ref_or_oid_value = ref_or_oid_value,│ │42 .issues_tag = issues_tag, │ │43 + .patches_tag = patches_tag, │ │44 .discussions_tag = discussions_tag, │ │45 }; │ │46 } │ ╰─────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │138 const refs_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try refs_route.toUrl(session.page_arena)}); │ │139 const issues_route = ui.RoutablePage.repoIssuesRoute(identity, .open, data.issues_tag, "") orelse return error.RouteTooLong; │ │140 const issues_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try issues_route.toUrl(session.page_arena)}); │ │141 + const patches_route = ui.RoutablePage.repoPatchesRoute(identity, .open, data.patches_tag, "") orelse return error.RouteTooLong; │ │142 + const patches_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try patches_route.toUrl(session.page_arena)}); │ │143 const discussions_route = ui.RoutablePage.repoDiscussionsRoute(identity, data.discussions_tag, "") orelse return error.RouteTooLong;│ │144 const discussions_link = try std.fmt.allocPrint(aa, "ai:{s}", .{try discussions_route.toUrl(session.page_arena)}); │ │145 const events_route = ui.RoutablePage.repoEventsRoute(identity, .active, null, "") orelse return error.RouteTooLong; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────╮ │155 .repo_commits => commits_link, │ │156 .repo_refs => refs_link, │ │157 .repo_issues => issues_link, │ │158 + .repo_patches => patches_link, │ │159 .repo_discussions => discussions_link,│ │160 .repo_events => events_link, │ │161 .repo_settings => settings_link, │ ╰───────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │221 .widget = .{ .text_box = text_box }, │ │222 .rect = null, │ │223 .min_size = .{ .width = "issues".len + 2, .height = null }, │ │224 + }); │ │225 + } │ │226 + │ │227 + // patches tab │ │228 + { │ │229 + var text_box = try wgt.TextBox(ui.Widget).init(allocator, "patches", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │230 + errdefer text_box.deinit(allocator); │ │231 + text_box.getFocus().focusable = true; │ │232 + text_box.getFocus().kind = .{ .custom = patches_link }; │ │233 + try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │234 + if (std.mem.eql(u8, patches_link, current_link)) selected_tab = text_box.getFocus().id; │ │235 + try tabs_box.children.put(allocator, text_box.getFocus().id, .{ │ │236 + .widget = .{ .text_box = text_box }, │ │237 + .rect = null, │ │238 + .min_size = .{ .width = "patches".len + 2, .height = null }, │ │239 }); │ │240 } │ │241 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────╮ │ 5 const xit = @import("xit"); │ │ 6 const rp = xit.repo; │ │ 7 const hash = xit.hash; │ │ 8 -const xitui = xit.xitui; │ │ 9 -const wgt = xitui.widget; │ │10 -const layout = xitui.layout; │ │11 -const Key = xitui.input.Key; │ │12 -const Grid = xitui.grid.Grid; │ │13 -const Focus = xitui.focus.Focus; │ │14 -const inp = @import("../input.zig"); │ │ 8 const diff3 = @import("../../diff3.zig"); │ │ 9 const Comment = @import("Comment.zig"); │ │10 const Attachment = @import("Attachment.zig");│ ╰─────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │218 var open_set: ?DB.SortedSet(.read_only) = null; │ │219 var closed_set: ?DB.SortedSet(.read_only) = null; │ │220 if (tagged) { │ │228 - const tag_to_issues_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "tag+status->issue-id-set")) orelse return error.NotFound; │ │221 + const tag_to_issues_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.tag_status_to_id_set_key)) orelse return error.NotFound;│ │222 const tag_to_issues = try DB.SortedMap(.read_only).init(tag_to_issues_cursor); │ │223 const decoded = std.Uri.percentDecodeInPlace(try aa.dupe(u8, empty.tag)); │ │231 - open_set = try tagStatusSet(DB, tag_to_issues, decoded, .open); │ │232 - closed_set = try tagStatusSet(DB, tag_to_issues, decoded, .closed); │ │224 + open_set = try Threads.tagStatusSet(Self, DB, tag_to_issues, decoded, .open); │ │225 + closed_set = try Threads.tagStatusSet(Self, DB, tag_to_issues, decoded, .closed); │ │226 // a tag no issue carries is a bad url │ │227 if (open_set == null and closed_set == null) return error.NotFound; │ │235 - } else if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "status->issue-id-set"))) |status_to_issues_cursor| { │ │228 + } else if (try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.status_to_id_set_key))) |status_to_issues_cursor| { │ │229 const status_to_issues = try DB.SortedMap(.read_only).init(status_to_issues_cursor); │ │237 - open_set = try statusSet(DB, status_to_issues, .open); │ │238 - closed_set = try statusSet(DB, status_to_issues, .closed); │ │230 + open_set = try Threads.statusSet(Self, DB, status_to_issues, .open); │ │231 + closed_set = try Threads.statusSet(Self, DB, status_to_issues, .closed); │ │232 } else if (rooted) return error.NotFound; │ │233 │ │241 - const event_id_to_issue_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "event-id->issue")) orelse { │ │234 + const event_id_to_issue_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, evt.Issue.record_map_key)) orelse { │ │235 if (strict) return error.NotFound; │ │236 return empty; │ │237 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │254 var conflicts_root: ?[]const u8 = null; │ │255 var conflict_data: ?Conflict = null; │ │256 if (rooted) { │ │264 - if (empty.selected_id.len != evt.event_id_size * 2) return error.NotFound; │ │265 - var id_bytes: [evt.event_id_size]u8 = undefined; │ │266 - _ = std.fmt.hexToBytes(&id_bytes, empty.selected_id) catch return error.NotFound; │ │257 + const id_bytes = evt.parseEventId(empty.selected_id) catch return error.NotFound; │ │258 const issue_cursor = try event_id_to_issue.getCursor(hash.hashInt(repo_opts.hash, &id_bytes)) orelse return error.NotFound;│ │259 const issue_map = try DB.HashMap(.read_only).init(issue_cursor); │ │260 const issue_event = try evt.read(evt.Issue.Record, DB, repo_opts.hash, arena, issue_map); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯