╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 96 }; │ │ 97 }, │ │ 98 .repo => switch (route) { │ │ 99 - .repo_files, .repo_commits, .repo_diff, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo, .repo_repo, .repo_repo_new, .repo_user_new, .repo_user => .{ .repo = try Repo.init(arena, session, route) }, │ │ 99 + .repo_files, .repo_commits, .repo_diff, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo, .repo_settings, .repo_roles, .repo_repo_new, .repo_user_new, .repo_user => .{ .repo = try Repo.init(arena, session, route) },│ │100 else => return error.UnexpectedRoute, │ │101 }, │ │102 .fork => switch (route) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │190 repo_undo: RepoUndoRoute, │ │191 repo_repo_new: Array(repo_route_max_len),│ │192 repo_user_new: Array(repo_route_max_len),│ │193 - repo_repo: Array(repo_route_max_len), │ │193 + repo_settings: Array(repo_route_max_len),│ │194 + repo_roles: RepoRolesRoute, │ │195 repo_user: Array(repo_route_max_len), │ │196 fork_patch: ForkRoute, │ │197 fork_diff: ForkDiffRoute, │ ╰──────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │222 │ │223 // the "key:value" segments of a url tail, collected in any order │ │224 const Params = struct { │ │224 - const ParamKey = enum { start, line, branch, tag, label, object, base, patchrev, theirs, find, search, from, moment, merge }; │ │225 + const ParamKey = enum { start, line, branch, tag, label, object, base, patchrev, theirs, find, search, from, moment, merge, user };│ │226 │ │227 // a branch:/tag:/object: param; the value stays url-encoded │ │228 const RefParam = struct { kind: RefOrOid, value: []const u8 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────╮ │316 const base_seg = @tagName(Params.ParamKey.base) ++ ":"; │ │317 const merge_seg = @tagName(Params.ParamKey.merge) ++ ":"; │ │318 const theirs_seg = @tagName(Params.ParamKey.theirs) ++ ":";│ │319 + const user_seg = @tagName(Params.ParamKey.user) ++ ":"; │ │320 const issue_seg = "issue:"; │ │321 const patch_seg = "patch:"; │ │322 const discuss_seg = "discuss:"; │ ╰────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │423 clear: bool = false, │ │424 }; │ │425 │ │424 - pub fn repoRepoRoute(identity: []const u8) ?RoutablePage { │ │425 - return .{ .repo_repo = Array(repo_route_max_len).from(identity) orelse return null }; │ │426 + pub const RepoRolesRoute = struct { │ │427 + name: Array(repo_identity_max_len), │ │428 + start: usize = 0, │ │429 + // the user a row's form acts on ("" = none) │ │430 + user: Array(evt.User.name_max_len) = .{}, │ │431 + }; │ │432 + │ │433 + pub fn repoSettingsRoute(identity: []const u8) ?RoutablePage { │ │434 + return .{ .repo_settings = Array(repo_route_max_len).from(identity) orelse return null }; │ │435 + } │ │436 + │ │437 + pub fn repoRolesRoute(identity: []const u8, start: usize) ?RoutablePage { │ │438 + return .{ .repo_roles = .{ .name = Array(repo_identity_max_len).from(identity) orelse return null, .start = start } };│ │439 } │ │440 │ │441 pub fn repoUndoRoute(identity: []const u8, index: ?u64) ?RoutablePage { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1251 }, │ │1252 .repo_repo_new => |name| try arena.allocator().print("{s}/repo/new", .{try repoUrlPrefix(arena, name.slice())}), │ │1253 .repo_user_new => |name| try arena.allocator().print("{s}/user/new", .{try repoUrlPrefix(arena, name.slice())}), │ │1241 - .repo_repo => |name| try arena.allocator().print("{s}/repo", .{try repoUrlPrefix(arena, name.slice())}), │ │1254 + .repo_settings => |name| try arena.allocator().print("{s}/repo/settings", .{try repoUrlPrefix(arena, name.slice())}),│ │1255 + .repo_roles => |r| blk: { │ │1256 + var out: std.Io.Writer.Allocating = .init(arena.allocator()); │ │1257 + try out.writer.print("{s}/repo/roles", .{try repoUrlPrefix(arena, r.name.slice())}); │ │1258 + if (r.user.len != 0) try out.writer.print("/" ++ user_seg ++ "{s}", .{r.user.slice()}); │ │1259 + if (r.start != 0) try out.writer.print("/" ++ start_seg ++ "{d}", .{r.start}); │ │1260 + break :blk out.written(); │ │1261 + }, │ │1262 .repo_user => |name| try arena.allocator().print("{s}/user", .{try repoUrlPrefix(arena, name.slice())}), │ │1263 .fork_patch => |f| try arena.allocator().print("/{s}+{s}", .{ f.name.slice(), f.id.slice() }), │ │1264 .fork_diff => |d| blk: { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1410 .repo_discussions => |*t| t.name.slice(), │ │1411 .repo_events => |*e| e.name.slice(), │ │1412 .repo_undo => |*u| u.name.slice(), │ │1393 - .repo_repo, .repo_repo_new, .repo_user_new, .repo_user => |*name| name.slice(), │ │1413 + .repo_roles => |*r| r.name.slice(), │ │1414 + .repo_settings, .repo_repo_new, .repo_user_new, .repo_user => |*name| name.slice(),│ │1415 else => null, │ │1416 }; │ │1417 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1454 return switch (self) { │ │1455 .home_about, .home_users, .home_repo_new, .home_user_new, .home_user => .home, │ │1456 .user_repos, .user_forks, .user_repo_new, .user_user_new, .user_user => .user, │ │1436 - .repo_files, .repo_commits, .repo_diff, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo, .repo_repo, .repo_repo_new, .repo_user_new, .repo_user => .repo, │ │1457 + .repo_files, .repo_commits, .repo_diff, .repo_refs, .repo_issues, .repo_patches, .repo_discussions, .repo_events, .repo_undo, .repo_settings, .repo_roles, .repo_repo_new, .repo_user_new, .repo_user => .repo,│ │1458 .fork_patch, .fork_diff, .fork_files, .fork_commits, .fork_repo_new, .fork_user_new, .fork_user => .fork, │ │1459 .not_found => .not_found, │ │1460 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1502 var segments = std.mem.splitScalar(u8, sub, '/'); │ │1503 const tab = segments.next() orelse return null; │ │1504 var params = Params{}; │ │1484 - if (std.mem.eql(u8, tab, "repo")) return if (segments.peek() == null) .{ .repo_repo = Array(repo_route_max_len).from(pair) orelse return null } else if (isNewTail(&segments)) .{ .repo_repo_new = Array(repo_route_max_len).from(pair) orelse return null } else null;│ │1505 + if (std.mem.eql(u8, tab, "repo")) { │ │1506 + const word = segments.peek() orelse return null; │ │1507 + if (std.mem.eql(u8, word, "settings")) { │ │1508 + _ = segments.next(); │ │1509 + return if (segments.next() == null) repoSettingsRoute(pair) else null; │ │1510 + } │ │1511 + if (std.mem.eql(u8, word, "roles")) { │ │1512 + _ = segments.next(); │ │1513 + const roles_params = listParams(&segments, &.{ .user, .start }) orelse return null; │ │1514 + var route = repoRolesRoute(pair, roles_params.start() orelse return null) orelse return null; │ │1515 + if (roles_params.values.get(.user)) |user| route.repo_roles.user = Array(evt.User.name_max_len).from(user) orelse return null; │ │1516 + return route; │ │1517 + } │ │1518 + return if (isNewTail(&segments)) .{ .repo_repo_new = Array(repo_route_max_len).from(pair) orelse return null } else null; │ │1519 + } │ │1520 if (std.mem.eql(u8, tab, "user")) return if (segments.peek() == null) .{ .repo_user = Array(repo_route_max_len).from(pair) orelse return null } else if (isNewTail(&segments)) .{ .repo_user_new = Array(repo_route_max_len).from(pair) orelse return null } else null;│ │1521 if (std.mem.eql(u8, tab, "refs")) { │ │1522 const word = params.scanTail(&segments) catch return null; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1829 std.mem.eql(u8, a_e.selected.slice(), b.repo_events.selected.slice()), │ │1830 .repo_repo_new => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_repo_new.slice()), │ │1831 .repo_user_new => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_user_new.slice()), │ │1797 - .repo_repo => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_repo.slice()), │ │1832 + .repo_settings => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_settings.slice()), │ │1833 + .repo_roles => |a_r| std.mem.eql(u8, a_r.name.slice(), b.repo_roles.name.slice()) and │ │1834 + a_r.start == b.repo_roles.start and │ │1835 + std.mem.eql(u8, a_r.user.slice(), b.repo_roles.user.slice()), │ │1836 .repo_user => |a_name| std.mem.eql(u8, a_name.slice(), b.repo_user.slice()), │ │1837 .fork_patch => |a_f| std.mem.eql(u8, a_f.name.slice(), b.fork_patch.name.slice()) and std.mem.eql(u8, a_f.id.slice(), b.fork_patch.id.slice()),│ │1838 .fork_diff => |a_d| std.mem.eql(u8, a_d.fork.name.slice(), b.fork_diff.fork.name.slice()) and │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯