╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │223 try stack.children.put(allocator, commits.getFocus().id, .{ .repo_commits = commits }); │ │224 } │ │225 const identity = try session.page_arena.allocator().print("{s}:{s}", .{ data.header.forker_name, data.header.name }); │ │226 + // right after commits, as in the header │ │227 + if (data.delete) |delete| { │ │228 + var view = try ui.Delete.View.init(allocator, delete, session, ui.RoutablePage.forkDeleteRoute(identity, data.header.id) orelse return error.RouteTooLong);│ │229 + errdefer view.deinit(allocator); │ │230 + try stack.children.put(allocator, view.getFocus().id, .{ .delete_form = view }); │ │231 + } │ │232 if (session.data.user_id != null) { │ │233 const route = ui.RoutablePage.forkRepoNewRoute(identity, data.header.id) orelse return error.RouteTooLong; │ │234 var new_repo = try ui.NewRepo.View.init(allocator, session, route); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Fork/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────╮ │14 const patch_tab_label = "patch"; │ │15 const diff_tab_label = "diff"; │ │16 const files_tab_label = "files"; │ │17 +const delete_tab_label = "delete fork"; │ │18 const new_tab_label = "new"; │ │19 const new_repo_tab_bottom_label = "repo";│ │20 const new_user_tab_bottom_label = "user";│ ╰─────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │46 first_group_width: usize, │ │47 session: *ui.Session, │ │48 │ │48 - pub fn init(allocator: std.mem.Allocator, data: *const Self, commit_count: ?u64, session: *ui.Session) !View { │ │49 + // `deletable` shows the delete tab, which only the forker may use │ │50 + pub fn init(allocator: std.mem.Allocator, data: *const Self, commit_count: ?u64, deletable: bool, session: *ui.Session) !View {│ │51 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border = .hidden, .round_corners = true, .direction = .horiz }); │ │52 errdefer box.deinit(allocator); │ │53 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │103 ui.RoutablePage.forkDiffRoute(fork_identity, data.id, "", "", 0, "") orelse return error.RouteTooLong, │ │104 ui.RoutablePage.forkFilesRoute(fork_identity, data.id, data.oid, "", 0) orelse return error.RouteTooLong, │ │105 ui.RoutablePage.forkCommitsRoute(fork_identity, data.id, data.oid) orelse return error.RouteTooLong, │ │106 + ui.RoutablePage.forkDeleteRoute(fork_identity, data.id) orelse return error.RouteTooLong, │ │107 }; │ │105 - const tags = [_]std.meta.Tag(ui.RoutablePage){ .fork_patch, .fork_diff, .fork_files, .fork_commits }; │ │106 - const labels = [_][]const u8{ patch_tab_label, diff_tab_label, files_tab_label, commits_tab_label }; │ │108 + const tags = [_]std.meta.Tag(ui.RoutablePage){ .fork_patch, .fork_diff, .fork_files, .fork_commits, .fork_delete }; │ │109 + const labels = [_][]const u8{ patch_tab_label, diff_tab_label, files_tab_label, commits_tab_label, delete_tab_label };│ │110 // a single commit's diff names the commit under its tab, and commits │ │111 // shows its count there │ │112 const bottom_labels = [_][]const u8{ "", switch (session.data.current_page) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │113 .fork_diff => |*d| if (d.oid.len == 0) "" else try ui.clippedBottomLabel(try aa.alloc(u8, ui.clipped_bottom_label_max_len), d.oid.slice()), │ │114 else => "", │ │112 - }, "", if (commit_count) |count| try aa.print("({d})", .{count}) else "" }; │ │115 + }, "", if (commit_count) |count| try aa.print("({d})", .{count}) else "", "" }; │ │116 var selected_tab: ?usize = null; │ │117 │ │115 - for (routes, tags, labels, bottom_labels) |route, tag, label, bottom_label| { │ │118 + // the delete tab is last, so leaving it out keeps the stack's order │ │119 + const tab_count = if (deletable) routes.len else routes.len - 1; │ │120 + for (routes[0..tab_count], tags[0..tab_count], labels[0..tab_count], bottom_labels[0..tab_count]) |route, tag, label, bottom_label| { │ │121 const selected = current_tag == tag; │ │122 var tab = try wgt.TextBox.init(allocator, label, .{ .border = .single, .round_corners = true, .wrap_kind = .none, .bottom_label = .{ .text = bottom_label } });│ │123 errdefer tab.deinit(allocator); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │318 if (session.data.host_kind == .server and handle.canUndo()) { │ │319 // a server page always resolved its repo id │ │320 const repo_id = repo_id_maybe orelse unreachable; │ │321 + const identity = try arena.allocator().dupe(u8, repo_identity.identity); │ │322 + // only the repo's creator may delete it, never a granted owner │ │323 + const creator = if (session.userId()) |user_id| std.mem.eql(u8, &user_id, &owner_id) else false;│ │324 settings = .{ │ │322 - .identity = try arena.allocator().dupe(u8, repo_identity.identity), │ │325 + .identity = identity, │ │326 .name = repo.event.name, │ │327 .description = repo.event.description, │ │328 .access = repo.event.read_access, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │333 .view = switch (route) { │ │334 .repo_members => .members, │ │335 .repo_branches => .branches, │ │336 + .repo_delete => .delete, │ │337 else => .settings, │ │338 }, │ │339 .members = try Members.init(io, gpa, arena, session.haxy_moment orelse return error.NoMoment, session.users_dir orelse return error.NoMoment, repo_identity.identity, &owner_id, &repo_id, members_route.start, if (members_route.confirm) |action| .{ .action = action, .name = members_route.user.slice() } else null),│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │340 .branches = try Branches.init(arena, repo_identity.identity, repo.event),│ │341 + .delete = if (creator) .{ .target = .{ .repo = identity } } else null, │ │342 }; │ │343 } │ │344 // tabs switch in-page, so every tab's data is read here │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │402 }; │ │403 }; │ │404 if (route == .repo_undo and undo_data == null) return error.NotFound; │ │400 - if ((route == .repo_settings or route == .repo_members or route == .repo_branches) and settings == null) return error.NotFound; │ │405 + if ((route == .repo_settings or route == .repo_members or route == .repo_branches or route == .repo_delete) and settings == null) return error.NotFound;│ │406 + if (settings) |value| if (route == .repo_delete and value.delete == null) return error.NotFound; │ │407 discussions.repo_source = source; │ │408 issues.repo_source = source; │ │409 patches.repo_source = source; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │303 var text_box = try wgt.TextBox.init(allocator, ui.Repo.Settings.tab_label, .{ .border = .single, .round_corners = true, .wrap_kind = .none }); │ │304 errdefer text_box.deinit(allocator); │ │305 text_box.getFocus().mode = .all; │ │306 - const selected = current_tag == .repo_settings or current_tag == .repo_members or current_tag == .repo_branches; │ │306 + const selected = current_tag == .repo_settings or current_tag == .repo_members or current_tag == .repo_branches or current_tag == .repo_delete;│ │307 text_box.getFocus().kind = .{ .custom = try ui.inPageTabLink(session, route, selected) }; │ │308 try tab_ids.put(allocator, text_box.getFocus().id, {}); │ │309 if (selected) selected_tab = text_box.getFocus().id; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯