╭ changed ──────╮ │src/ui/Fork.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────╮ │19 pub const Patches = @import("./Repo/Patches.zig"); │ │20 pub const Auth = @import("./Auth.zig"); │ │21 pub const Quit = @import("./Quit.zig"); │ │22 +const Viewer = @import("./Repo/Comment.zig").Viewer;│ │23 │ │24 header: Header, │ │25 files: Files, │ ╰────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │101 patch_data.repo_source = target_source; │ │102 }, │ │103 .publish => { │ │103 - patch_data = Patches.init(.xit, repo_opts, arena, target_repo, io, haxy_moment, session, target_id, target_identity, target_branch, "", "", &id_hex, "", 0, "", .open) catch |err| switch (err) { │ │104 + patch_data = Patches.init(.xit, repo_opts, arena, target_repo, io, haxy_moment, session, target_id, target_identity, target_branch, "", "", &id_hex, "", 0, "", .open, try Viewer.init(session, arena, target_record)) catch |err| switch (err) {│ │105 error.NotFound => patch_data, │ │106 else => |other| return other, │ │107 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │263 .repo_kind = .xit, │ │264 }; │ │265 } │ │266 + │ │267 + const viewer = try Comment.Viewer.init(session, arena, repo); │ │268 │ │269 // open the repo once for every tab. files and changes share a ref or revision.│ │270 // no filesystem (wasm), nowhere to look, or a failed open: empty tabs. │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │321 files_data, │ │322 changes_data, │ │323 try Refs.init(repo_kind, opened.self_repo_opts, arena, opened, io, gpa, repo_identity.identity, refs_kind, refs_from, refs_search), │ │322 - try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, issues_label, issues_search, issues_selected, issues_comment, issues_comments_start, issues_theirs, issues_view), │ │323 - try Patches.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, session, repo_id_maybe, repo_identity.identity, target_branch, patches_label, patches_search, patches_selected, patches_comment, patches_comments_start, patches_theirs, patches_view), │ │324 - try Discussions.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, discussions_label, discussions_search, discussions_selected, discussions_comment, discussions_comments_start, discussions_view), │ │324 + try Issues.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, issues_label, issues_search, issues_selected, issues_comment, issues_comments_start, issues_theirs, issues_view, viewer), │ │325 + try Patches.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, session, repo_id_maybe, repo_identity.identity, target_branch, patches_label, patches_search, patches_selected, patches_comment, patches_comments_start, patches_theirs, patches_view, viewer),│ │326 + try Discussions.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, discussions_label, discussions_search, discussions_selected, discussions_comment, discussions_comments_start, discussions_view, viewer), │ │327 try Events.init(repo_kind, opened.self_repo_opts, arena, opened, io, session.haxy_moment, repo_identity.identity, events_view, events_kind, events_selected, events_moment, session.local != null, session.data.sync_failure), │ │328 }; │ │329 }, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Comment.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │17 comment: evt.Comment.Record, │ │18 author: ui.Author = .unknown, │ │19 parent_author: ?ui.Author = null, │ │20 + // whether the viewer may edit or remove the comment │ │21 + can_modify: bool = false, │ │22 +}; │ │23 + │ │24 +// who a thread page is built for, which picks the comment buttons it shows │ │25 +pub const Viewer = struct { │ │26 + role: evt.Repo.Role, │ │27 + email: []const u8, │ │28 + │ │29 + // the session's viewer of `repo`, or null when logged out. local mode owns everything. │ │30 + pub fn init(session: *ui.Session, arena: *std.heap.ArenaAllocator, repo: evt.Repo.Record) !?Viewer {│ │31 + if (session.data.host_kind == .local) return .{ .role = .owner, .email = "" }; │ │32 + const user_id = session.userId() orelse return null; │ │33 + const moment = session.haxy_moment orelse unreachable; │ │34 + const user = (try ui.activeUser(moment, arena, user_id)) orelse return null; │ │35 + return .{ .role = evt.Repo.roleOf(repo, user_id), .email = user.event.email }; │ │36 + } │ │37 }; │ │38 │ │39 pub const Window = struct { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │58 thread_id: []const u8, │ │59 selected_id: []const u8, │ │60 start: usize, │ │61 + viewer: ?Viewer, │ │62 ) !Permalink { │ │45 - const selected = (try readOne(hash_kind, arena, admin_moment, haxy_moment, selected_id)) orelse return error.NotFound; │ │63 + const selected = (try readOne(hash_kind, arena, admin_moment, haxy_moment, selected_id, viewer)) orelse return error.NotFound;│ │64 if (!std.mem.eql(u8, &selected.comment.event.thread_id, thread_id)) return error.NotFound; │ │65 │ │66 return .{ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │67 .selected = selected, │ │50 - .replies = try loadWindow(hash_kind, arena, admin_moment, haxy_moment, evt.Comment.parent_id_to_comment_id_set_key, selected_id, start), │ │68 + .replies = try loadWindow(hash_kind, arena, admin_moment, haxy_moment, evt.Comment.parent_id_to_comment_id_set_key, selected_id, start, viewer),│ │69 }; │ │70 } │ │71 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │78 index_key: []const u8, │ │79 owner_id: []const u8, │ │80 start: usize, │ │81 + viewer: ?Viewer, │ │82 ) !Window { │ │83 const DB = evt.EventDB(hash_kind); │ │84 const index_cursor = try haxy_moment.getCursor(hash.hashInt(hash_kind, index_key)) orelse return .{│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │104 var order_key: [@sizeOf(u64) + evt.event_id_size]u8 = undefined; │ │105 _ = try kv.key_cursor.readBytes(&order_key); │ │106 const id_bytes = order_key[@sizeOf(u64)..].*; │ │ 88 - const entry = (try readOneBytes(hash_kind, arena, admin_moment, haxy_moment, &id_bytes)) orelse continue; │ │107 + const entry = (try readOneBytes(hash_kind, arena, admin_moment, haxy_moment, &id_bytes, viewer)) orelse continue;│ │108 try comments.append(arena.allocator(), entry); │ │109 } │ │110 return .{ │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │120 admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │121 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), │ │122 id: []const u8, │ │123 + viewer: ?Viewer, │ │124 ) !?CommentWithId { │ │125 const bytes = idBytes(id) orelse return null; │ │106 - return readOneBytes(hash_kind, arena, admin_moment, haxy_moment, &bytes); │ │126 + return readOneBytes(hash_kind, arena, admin_moment, haxy_moment, &bytes, viewer);│ │127 } │ │128 │ │129 fn readOneBytes( │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯