╭ changed ──────────────╮ │src/ui/Repo/Comment.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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( │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │132 admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │133 haxy_moment: evt.EventDB(hash_kind).HashMap(.read_only), │ │134 id_bytes: *const [evt.event_id_size]u8, │ │135 + viewer: ?Viewer, │ │136 ) !?CommentWithId { │ │137 const DB = evt.EventDB(hash_kind); │ │138 const comment = (try evt.Comment.readById(DB, hash_kind, haxy_moment, arena, id_bytes)) orelse return null;│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │146 .comment = comment, │ │147 .author = try ui.Author.initFromEmail(admin_moment, arena, comment.author_email), │ │148 .parent_author = parent_author, │ │149 + .can_modify = if (viewer) |v| v.role.canModify(v.email, comment.author_email) else false,│ │150 }; │ │151 } │ │152 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │165 const body_index: usize = 1; │ │166 const gap_index: usize = 2; │ │167 │ │146 - pub fn init(allocator: std.mem.Allocator, session: *ui.Session, identity: []const u8, thread_kind: evt.EventKind, entry: CommentWithId) !Item { │ │168 + pub fn init(allocator: std.mem.Allocator, session: *ui.Session, identity: []const u8, thread_kind: evt.EventKind, can_reply: bool, entry: CommentWithId) !Item {│ │169 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │170 errdefer box.deinit(allocator); │ │171 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │182 errdefer spacer.deinit(allocator); │ │183 try bar.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = null }); │ │184 │ │163 - var reply = try linkBox(allocator, session, "new reply", commentNewRoute(thread_kind, identity, &entry.comment.event.thread_id, &entry.id) orelse return error.RouteTooLong); │ │164 - errdefer reply.deinit(allocator); │ │165 - try bar.children.put(allocator, reply.getFocus().id, .{ .widget = .{ .text_box = reply }, .rect = null, .min_size = .{ .width = "new reply".len + 2, .height = null } }); │ │185 + if (can_reply) { │ │186 + var reply = try linkBox(allocator, session, "new reply", commentNewRoute(thread_kind, identity, &entry.comment.event.thread_id, &entry.id) orelse return error.RouteTooLong); │ │187 + errdefer reply.deinit(allocator); │ │188 + try bar.children.put(allocator, reply.getFocus().id, .{ .widget = .{ .text_box = reply }, .rect = null, .min_size = .{ .width = "new reply".len + 2, .height = null } }); │ │189 + } │ │190 │ │167 - if (!entry.comment.removed) { │ │191 + if (entry.can_modify and !entry.comment.removed) { │ │192 var edit = try linkBox(allocator, session, "edit comment", commentEditRoute(thread_kind, identity, &entry.comment.event.thread_id, &entry.id) orelse return error.RouteTooLong);│ │193 errdefer edit.deinit(allocator); │ │194 try bar.children.put(allocator, edit.getFocus().id, .{ .widget = .{ .text_box = edit }, .rect = null, .min_size = .{ .width = "edit comment".len + 2, .height = null } }); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯