╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │354 const supports_conflicts = Event.merge_policy == .field_conflicts;│ │355 const supports_drafts = @hasField(Data, "drafts"); │ │356 const supports_forks = @hasField(Entry, "fork_exists"); │ │357 + const supports_merge = @hasDecl(Data, "mergeRoute"); │ │358 return struct { │ │359 const This = @This(); │ │360 const thread_name = Data.thread_name; │ ╰───────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │472 │ │473 fn publishRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │474 return if (supports_drafts) Data.publishRoute(identity, selected) else null;│ │475 + } │ │476 + │ │477 + fn mergeRoute(identity: []const u8, selected: []const u8) ?ui.RoutablePage { │ │478 + return if (supports_merge) Data.mergeRoute(identity, selected) else null; │ │479 } │ │480 │ │481 fn forkRoute(identity: []const u8, id: []const u8) ?ui.RoutablePage { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │534 var spacer = try Spacer.init(allocator); │ │535 errdefer spacer.deinit(allocator); │ │536 try row.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = null }); │ │537 + } │ │538 + │ │539 + if (comptime supports_merge) { │ │540 + if (Data.canMerge(entry, self.session)) { │ │541 + const route = mergeRoute(self.data.identity, entry.id) orelse return error.RouteTooLong; │ │542 + try addToolButton(allocator, row, "merge", try std.fmt.allocPrint(pa, "a:{s}", .{try route.toUrl(self.session.page_arena)}));│ │543 + } │ │544 } │ │545 │ │546 if (supports_drafts and entryDraft(entry)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │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 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯