╭ changed ────────────╮ │src/ui/Home/Repos.zig│ ╰─────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────╮ │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 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯