╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Threads.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1072 row.getFocus().focusable = true; │ │1073 if (link.len != 0) row.getFocus().kind = .{ .custom = link }; │ │1074 try box.children.put(allocator, row.getFocus().id, .{ .widget = .{ .text_box = row }, .rect = null, .min_size = null, .max_size = .{ .width = null, .height = 5 } }); │ │1075 + } │ │1076 + │ │1077 + fn addToolButton(allocator: std.mem.Allocator, row: *wgt.Box(ui.Widget), label: []const u8, action: []const u8) !void { │ │1078 + var button = try wgt.TextBox(ui.Widget).init(allocator, label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │1079 + errdefer button.deinit(allocator); │ │1080 + button.getFocus().focusable = true; │ │1081 + button.getFocus().kind = .{ .custom = action }; │ │1082 + try row.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = .{ .width = try xitui.width.displayWidth(label) + 2, .height = null } });│ │1083 } │ │1084 │ │1085 pub fn deinit(self: *This, allocator: std.mem.Allocator) void { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │1109 return &self.viewStack().children.values()[tags_view_index].tag_flow; │ │1110 } │ │1111 │ │1147 - // the new-issue, edit, or resolve form inside the stack, or null when the │ │1112 + // the new-thread, edit, or resolve form inside the stack, or null when the│ │1113 // unauthorized view stands in for it. │ │1114 fn threadForm(self: *This) ?*wgt.Box(ui.Widget) { │ │1115 return switch (self.viewStack().children.values()[form_view_index]) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1179 } │ │1180 │ │1181 // the stack's selected master-detail split (null when the tags view or the │ │1217 - // new-issue form shows). │ │1182 + // new-thread form shows). │ │1183 fn selectedSplitIndex(self: *This) ?usize { │ │1184 const idx = self.stackSelectedIndex() orelse return null; │ │1185 return if (idx < status_count or (has_drafts and idx == drafts_view_index) or idx == conflict_view_index) idx else null;│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │1206 return self.stackSelectedIndex() == form_view_index; │ │1207 } │ │1208 │ │1244 - // the selected issue's index, or null when a window-navigation row is │ │1245 - // selected (a leading "previous" row shifts the issue rows down by one). │ │1209 + // the selected thread's index, or null when a window-navigation row is │ │1210 + // selected (a leading "previous" row shifts the thread rows down by one).│ │1211 fn selectedThreadIndex(self: *This, index: usize) ?usize { │ │1212 const lb = self.listBox(index); │ │1213 const cid = lb.getFocus().child_id orelse return null; │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1230 stack.getFocus().child_id = stack.children.keys()[index]; │ │1231 self.session.data.current_page = (if (index == form_view_index) │ │1232 (if (has_conflicts) switch (self.data.view) { │ │1268 - .edit => editRoute(self.data.identity, self.data.selected_id), │ │1269 - .new_comment => newCommentRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1270 - .edit_comment => editCommentRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1271 - .remove => removeRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1233 + .edit => ui.RoutablePage.repoThreadEditRoute(kind, self.data.identity, self.data.selected_id), │ │1234 + .new_comment => ui.RoutablePage.repoThreadCommentNewRoute(kind, self.data.identity, self.data.selected_id, self.data.comment_id), │ │1235 + .edit_comment => ui.RoutablePage.repoThreadCommentEditRoute(kind, self.data.identity, self.data.selected_id, self.data.comment_id),│ │1236 + .remove => ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, self.data.selected_id, self.data.comment_id), │ │1237 .resolve => resolveRoute(self.data.identity, self.data.selected_id, self.data.theirs_picks), │ │1273 - else => newRoute(self.data.identity), │ │1238 + else => ui.RoutablePage.repoThreadNewRoute(kind, self.data.identity), │ │1239 } else switch (self.data.view) { │ │1275 - .edit => editRoute(self.data.identity, self.data.selected_id), │ │1276 - .new_comment => newCommentRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1277 - .edit_comment => editCommentRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1278 - .remove => removeRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1279 - else => newRoute(self.data.identity), │ │1240 + .edit => ui.RoutablePage.repoThreadEditRoute(kind, self.data.identity, self.data.selected_id), │ │1241 + .new_comment => ui.RoutablePage.repoThreadCommentNewRoute(kind, self.data.identity, self.data.selected_id, self.data.comment_id), │ │1242 + .edit_comment => ui.RoutablePage.repoThreadCommentEditRoute(kind, self.data.identity, self.data.selected_id, self.data.comment_id),│ │1243 + .remove => ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, self.data.selected_id, self.data.comment_id), │ │1244 + else => ui.RoutablePage.repoThreadNewRoute(kind, self.data.identity), │ │1245 }) │ │1246 else if (has_conflicts and index == conflict_view_index) │ │1247 conflictsRoute(self.data.identity, if (self.data.view == .conflicts) self.data.selected_id else "") │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1248 else if (has_drafts and index == drafts_view_index) │ │1249 if (index == viewIndex(self.data.view) and self.data.selected_id.len != 0) │ │1285 - commentsRoute(self.data.identity, self.data.selected_id, 0) │ │1250 + ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, self.data.selected_id, 0)│ │1251 else │ │1252 draftsRoute(self.data.identity) │ │1253 else if (index == viewIndex(self.data.view) and self.data.selected_id.len != 0) │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1254 (if (self.data.description_page) │ │1290 - descriptionRoute(self.data.identity, self.data.selected_id) │ │1255 + ui.RoutablePage.repoThreadDescriptionRoute(kind, self.data.identity, self.data.selected_id) │ │1256 else if (self.data.comment_page) |page| │ │1292 - commentRoute(self.data.identity, self.data.selected_id, &page.selected.id, page.replies.start) │ │1257 + ui.RoutablePage.repoThreadCommentRoute(kind, self.data.identity, self.data.selected_id, &page.selected.id, page.replies.start)│ │1258 else │ │1294 - commentsRoute(self.data.identity, self.data.selected_id, self.data.comments_start)) │ │1259 + ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, self.data.selected_id, self.data.comments_start)) │ │1260 else if (index == tags_view_index) │ │1296 - tagsRoute(self.data.identity, self.data.tag) │ │1261 + ui.RoutablePage.repoThreadTagsRoute(kind, self.data.identity, self.data.tag) │ │1262 else │ │1263 listRoute(self.data.identity, splitStatus(index), self.data.tag, "")) orelse self.session.data.current_page; │ │1264 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1265 │ │1266 if (self.selectedSplitIndex()) |i| { │ │1302 - // swap the detail pane to the selected issue when it changes. │ │1303 - try self.refreshDetail(allocator, i); │ │1267 + // swap the detail pane to the selected thread when it changes. │ │1268 + if (self.selectedThreadIndex(i)) |selected| { │ │1269 + const changed = if (self.detailed_index[i]) |current| current != selected else true;│ │1270 + if (changed) { │ │1271 + try self.populateDetail(allocator, i, selected); │ │1272 + self.detailed_index[i] = selected; │ │1273 + } │ │1274 + } │ │1275 │ │1305 - // mirror the focused issue into the url, but only while focus is │ │1306 - // inside the split. an issue's url is the same whether or not the │ │1276 + // mirror the focused thread into the url, but only while focus is │ │1277 + // inside the split. a thread's url is the same whether or not the │ │1278 // list is filtered, so the mirror drops the tag. │ │1279 if (root_focus.grandchild_id) |g| { │ │1280 if (self.resultsBox(i).getFocus().children.contains(g)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1281 if (self.selectedThreadIndex(i)) |sel| { │ │1311 - // selecting another issue leaves the description page │ │1282 + // selecting another thread leaves the description page │ │1283 // behind, like it leaves the tag filter behind. │ │1284 const entry = &self.window(i).items[sel]; │ │1285 const mirrored = if (self.data.description_page and std.mem.eql(u8, entry.id, self.data.selected_id))│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1315 - descriptionRoute(self.data.identity, entry.id) │ │1286 + ui.RoutablePage.repoThreadDescriptionRoute(kind, self.data.identity, entry.id) │ │1287 else if (self.data.comment_page) |page| │ │1288 if (std.mem.eql(u8, entry.id, self.data.selected_id)) │ │1318 - commentRoute(self.data.identity, entry.id, &page.selected.id, page.replies.start) │ │1289 + ui.RoutablePage.repoThreadCommentRoute(kind, self.data.identity, entry.id, &page.selected.id, page.replies.start)│ │1290 else │ │1320 - commentsRoute(self.data.identity, entry.id, 0) │ │1291 + ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) │ │1292 else if (std.mem.eql(u8, entry.id, self.data.selected_id)) │ │1322 - commentsRoute(self.data.identity, entry.id, entry.comments.start) │ │1293 + ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, entry.comments.start) │ │1294 else │ │1324 - commentsRoute(self.data.identity, entry.id, 0); │ │1295 + ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0); │ │1296 if (mirrored) |route| self.session.data.current_page = route; │ │1297 } │ │1298 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯