╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │1210 const has_status = @hasField(Event, "status"); │ │1211 const supports_conflicts = Event.merge_policy == .field_conflicts; │ │1212 const supports_drafts = @hasField(Self, "drafts"); │ │1213 + const supports_merge = @hasDecl(Data, "mergeRoute"); │ │1214 const FieldConflict = if (supports_conflicts) Data.FieldConflict else void;│ │1215 const ViewKind = Data.ViewKind; │ │1216 const thread_name = Data.thread_name; │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1255 pub fn viewIndex(view: ViewKind) usize { │ │1256 const name = @tagName(view); │ │1257 if (std.mem.eql(u8, name, "tags")) return tags_view_index; │ │1245 - if (std.mem.eql(u8, name, "new") or std.mem.eql(u8, name, "edit") or std.mem.eql(u8, name, "publish") or std.mem.eql(u8, name, "new_comment") or std.mem.eql(u8, name, "edit_comment") or std.mem.eql(u8, name, "remove") or std.mem.eql(u8, name, "resolve")) return form_view_index; │ │1258 + if (std.mem.eql(u8, name, "new") or std.mem.eql(u8, name, "edit") or std.mem.eql(u8, name, "publish") or std.mem.eql(u8, name, "merge") or std.mem.eql(u8, name, "new_comment") or std.mem.eql(u8, name, "edit_comment") or std.mem.eql(u8, name, "remove") or std.mem.eql(u8, name, "resolve")) return form_view_index;│ │1259 if (std.mem.eql(u8, name, "conflicts")) return conflict_view_index; │ │1260 if (std.mem.eql(u8, name, "drafts")) return drafts_view_index; │ │1261 if (std.mem.eql(u8, name, "description")) unreachable; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────╮ │1265 unreachable; │ │1266 } │ │1267 │ │1255 - const Confirmation = enum { remove, publish }; │ │1268 + const Confirmation = enum { remove, publish, merge };│ │1269 │ │1270 fn confirmation(view: ViewKind) ?Confirmation { │ │1271 if (view == .remove) return .remove; │ ╰───────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1272 if (supports_drafts and std.mem.eql(u8, @tagName(view), "publish")) return .publish;│ │1273 + if (supports_merge and std.mem.eql(u8, @tagName(view), "merge")) return .merge; │ │1274 return null; │ │1275 } │ │1276 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │1302 │ │1303 fn publishRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │1304 return if (supports_drafts) Data.publishRoute(identity, selected) else null;│ │1305 + } │ │1306 + │ │1307 + fn mergeRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │1308 + return if (supports_merge) Data.mergeRoute(identity, selected) else null; │ │1309 } │ │1310 │ │1311 fn conflictsRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1403 const route = switch (confirmation_kind) { │ │1404 .remove => ui.RoutablePage.repoThreadRemoveRoute(kind, data.identity, data.selected_id, data.comment_id),│ │1405 .publish => publishRoute(data.identity, data.selected_id), │ │1406 + .merge => mergeRoute(data.identity, data.selected_id), │ │1407 } orelse return error.RouteTooLong; │ │1389 - const action = try std.fmt.allocPrint(aa, "form:{s}", .{try route.toUrl(session.page_arena)}); │ │1408 + const page_url = try route.toUrl(session.page_arena); │ │1409 + const action = try std.fmt.allocPrint(aa, "form:{s}", .{page_url}); │ │1410 const event_name = if (data.comment_id.len == 0) thread_name else "comment"; │ │1411 var label_buf: ["remove ".len + @max(thread_name.len, "comment".len)]u8 = undefined; │ │1392 - const label = switch (confirmation_kind) { │ │1412 + var buttons: [2]ConfirmationButton = undefined; │ │1413 + buttons[0] = .{ .label = switch (confirmation_kind) { │ │1414 .remove => try std.fmt.bufPrint(&label_buf, "remove {s}", .{event_name}), │ │1415 .publish => "publish patch", │ │1395 - }; │ │1396 - var center = try initConfirmationForm(allocator, action, label); │ │1416 + .merge => "merge patch", │ │1417 + } }; │ │1418 + var button_count: usize = 1; │ │1419 + if (confirmation_kind == .merge) { │ │1420 + if (comptime supports_merge) { │ │1421 + buttons[1] = .{ │ │1422 + .label = "squash and merge patch", │ │1423 + .action = try std.fmt.allocPrint(aa, "{s}{s}/squash", .{ ui.submit_action_prefix, page_url }), │ │1424 + }; │ │1425 + button_count += 1; │ │1426 + } else unreachable; │ │1427 + } │ │1428 + var center = try initConfirmationForm(allocator, action, buttons[0..button_count]); │ │1429 errdefer center.deinit(allocator); │ │1430 try stack.children.put(allocator, center.getFocus().id, .{ .center = center }); │ │1431 } else if (data.view == .new_comment or data.view == .edit_comment) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1656 return box; │ │1657 } │ │1658 │ │1627 - fn initConfirmationForm(allocator: std.mem.Allocator, action: []const u8, label: []const u8) !Center { │ │1659 + const ConfirmationButton = struct { │ │1660 + label: []const u8, │ │1661 + action: []const u8 = "submit", │ │1662 + }; │ │1663 + │ │1664 + fn initConfirmationForm(allocator: std.mem.Allocator, action: []const u8, buttons: []const ConfirmationButton) !Center { │ │1665 var box = try wgt.Box(Widget).init(allocator, .{ .border_style = null, .rounded_corners = true, .direction = .vert });│ │1666 errdefer box.deinit(allocator); │ │1667 box.getFocus().kind = .{ .custom = action }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1670 errdefer prompt.deinit(allocator); │ │1671 try box.children.put(allocator, prompt.getFocus().id, .{ .widget = .{ .text = prompt }, .rect = null, .min_size = null }); │ │1672 │ │1636 - var button = try wgt.TextBox.init(allocator, label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │1637 - errdefer button.deinit(allocator); │ │1638 - button.getFocus().focusable = true; │ │1639 - button.getFocus().kind = .{ .custom = "submit" }; │ │1640 - try box.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = null }); │ │1641 - box.getFocus().child_id = button.getFocus().id; │ │1673 + for (buttons, 0..) |options, index| { │ │1674 + var button = try wgt.TextBox.init(allocator, options.label, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │1675 + errdefer button.deinit(allocator); │ │1676 + button.getFocus().focusable = true; │ │1677 + button.getFocus().kind = .{ .custom = options.action }; │ │1678 + try box.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = null }); │ │1679 + if (index == 0) box.getFocus().child_id = button.getFocus().id; │ │1680 + } │ │1681 │ │1682 return Center.init(allocator, .{ .box = box }); │ │1683 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2210 │ │2211 fn confirmationFormInput(self: *This, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │2212 const form = self.confirmationForm() orelse return; │ │2174 - const button_id = form.children.keys()[1]; │ │2213 + const child_id = form.getFocus().child_id orelse return; │ │2214 + const index = form.children.getIndex(child_id) orelse return; │ │2215 switch (key) { │ │2176 - .arrow_up, .back_tab => self.focusHeader(root_focus), │ │2177 - .enter => try self.submitConfirmation(allocator), │ │2178 - .mouse => |mouse| if (inp.leftClickOn(root_focus, button_id, mouse)) try self.submitConfirmation(allocator), │ │2216 + .arrow_up, .back_tab => if (index > 1) │ │2217 + root_focus.setFocus(form.children.keys()[index - 1]) │ │2218 + else │ │2219 + self.focusHeader(root_focus), │ │2220 + .arrow_down, .tab => if (index + 1 < form.children.count()) root_focus.setFocus(form.children.keys()[index + 1]),│ │2221 + .enter => try self.submitConfirmation(allocator, index), │ │2222 + .mouse => |mouse| for (form.children.keys()[1..], 1..) |button_id, button_index| { │ │2223 + if (inp.leftClickOn(root_focus, button_id, mouse)) { │ │2224 + try self.submitConfirmation(allocator, button_index); │ │2225 + break; │ │2226 + } │ │2227 + }, │ │2228 else => {}, │ │2229 } │ │2230 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2555 try self.session.navigate(route); │ │2556 } │ │2557 │ │2509 - fn submitConfirmation(self: *This, allocator: std.mem.Allocator) !void { │ │2558 + fn submitConfirmation(self: *This, allocator: std.mem.Allocator, button_index: usize) !void {│ │2559 switch (confirmation(self.data.view) orelse return) { │ │2560 .publish => if (comptime supports_drafts) { │ │2561 if (comptime wasm) return; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯