╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │211 const parseName = struct { │ │212 // errors must stay within std.json's ParseError set; ValueTooLong │ │213 // is its member for an over-long field. │ │167 - fn f(maybe: ?[]const u8) !Array(evt.User.name_max_len) { │ │168 - return Array(evt.User.name_max_len).from(maybe orelse return error.MissingField) orelse error.ValueTooLong;│ │214 + fn f(comptime max_len: usize, maybe: ?[]const u8) !Array(max_len) { │ │215 + return Array(max_len).from(maybe orelse return error.MissingField) orelse error.ValueTooLong; │ │216 } │ │217 }.f; │ │218 return switch (helper.kind) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │220 .home_repos => .home_repos, │ │221 .home_settings => .home_settings, │ │222 .home_auth => .home_auth, │ │176 - .user => .{ .user = try parseName(helper.name) }, │ │177 - .user_settings => .{ .user_settings = try parseName(helper.name) }, │ │178 - .user_auth => .{ .user_auth = try parseName(helper.name) }, │ │223 + .user => .{ .user = try parseName(evt.User.name_max_len, helper.name) }, │ │224 + .user_settings => .{ .user_settings = try parseName(evt.User.name_max_len, helper.name) },│ │225 + .user_auth => .{ .user_auth = try parseName(evt.User.name_max_len, helper.name) }, │ │226 + .repo => .{ .repo = try parseName(repo_name_max_len, helper.name) }, │ │227 + .repo_settings => .{ .repo_settings = try parseName(repo_name_max_len, helper.name) }, │ │228 + .repo_auth => .{ .repo_auth = try parseName(repo_name_max_len, helper.name) }, │ │229 }; │ │230 } │ │231 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────╮ │583 const page_widget: Widget = switch (page.*) { │ │584 .home => |*p| .{ .home = try .init(allocator, p, session) },│ │585 .user => |*p| .{ .user = try .init(allocator, p, session) },│ │586 + .repo => |*p| .{ .repo = try .init(allocator, p, session) },│ │587 }; │ │588 │ │589 const demon_art = @embedFile("embed/demon.ans"); │ ╰─────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────╮ │617 background: AnsiBackground, │ │618 home: Home.View, │ │619 user: User.View, │ │620 + repo: Repo.View, │ │621 quit: Quit.View, │ │622 title: Title.View, │ │623 home_header: Home.Header.View,│ ╰───────────────────────────────────────╯ ╭────────────────────────────────────────────╮ │624 user_header: User.Header.View, │ │625 + repo_header: Repo.Header.View, │ │626 home_users: Home.Users.View, │ │627 home_repos: Home.Repos.View, │ │628 auth_tab: Home.Header.AuthTab.View,│ ╰────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Home/Repos.zig│ ╰─────────────────────╯ ╭────────────────────────────────────╮ │11 const Focus = xitui.focus.Focus;│ │12 │ │13 repos: []const evt.Repo, │ │14 +owner_names: []const []const u8,│ │15 │ │16 const Self = @This(); │ │17 │ ╰────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │20 haxy_moment: evt.AdminDB.HashMap(.read_only), │ │21 ) !Self { │ │22 const DB = evt.AdminDB; │ │23 + const hash_kind = evt.admin_repo_opts.hash; │ │24 │ │25 var repos: std.ArrayList(evt.Repo) = .empty; │ │26 + var owner_names: std.ArrayList([]const u8) = .empty; │ │27 │ │25 - const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(evt.admin_repo_opts.hash, "event-id->repo")) orelse return error.NotFound;│ │28 + const event_id_to_repo_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, "event-id->repo")) orelse return error.NotFound; │ │29 const event_id_to_repo = try DB.HashMap(.read_only).init(event_id_to_repo_cursor); │ │30 │ │31 var repos_iter = try event_id_to_repo.iterator(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │32 while (try repos_iter.next()) |kv_cursor| { │ │33 const kv = try kv_cursor.readKeyValuePair(); │ │34 const repo_map = try DB.HashMap(.read_only).init(kv.value_cursor); │ │32 - const repo_event = try evt.read(evt.Repo, DB, evt.admin_repo_opts.hash, arena, repo_map); │ │35 + const repo_event = try evt.read(evt.Repo, DB, hash_kind, arena, repo_map); │ │36 try repos.append(arena.allocator(), repo_event); │ │37 + │ │38 + const owner = try evt.User.readById(DB, hash_kind, haxy_moment, arena, repo_event.user_id);│ │39 + try owner_names.append(arena.allocator(), if (owner) |o| o.name else ""); │ │40 } │ │41 │ │42 return .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────╮ │43 .repos = repos.items, │ │44 + .owner_names = owner_names.items,│ │45 }; │ │46 } │ │47 │ ╰─────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │66 const aa = arena.allocator(); │ │67 │ │68 const lines = try aa.alloc([]const u8, data.repos.len); │ │69 + const links = try aa.alloc([]const u8, data.repos.len); │ │70 for (data.repos, 0..) |repo, i| { │ │71 lines[i] = try std.fmt.allocPrint(aa, "{s} - {s}", .{ repo.name, repo.description }); │ │72 + // clicking a repo opens its page; the "a:" prefix makes the web │ │73 + // renderer emit an <a href="/repo/alice/foo"> anchor. skip the link │ │74 + // when the owner is unknown so it isn't a dead route. │ │75 + const owner = data.owner_names[i]; │ │76 + links[i] = if (owner.len > 0) try std.fmt.allocPrint(aa, "a:/repo/{s}/{s}", .{ owner, repo.name }) else "";│ │77 } │ │65 - try self.list.setItems(allocator, lines, null); │ │78 + try self.list.setItems(allocator, lines, links); │ │79 │ │80 return self; │ │81 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯