╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │558 if (supports_drafts and entryDraft(entry)) { │ │559 const route = publishRoute(self.data.identity, entry.id) orelse return error.RouteTooLong; │ │560 try addToolButton(allocator, row, "publish", "", try std.fmt.allocPrint(pa, "a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │561 - if (self.session.data.user_id != null) { │ │562 - const edit_route = ui.RoutablePage.repoThreadEditRoute(kind, self.data.identity, entry.id) orelse return error.RouteTooLong; │ │563 - try addToolButton(allocator, row, "edit", "", try std.fmt.allocPrint(pa, "a:{s}", .{try edit_route.toUrl(self.session.page_arena)})); │ │564 - const remove_route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │565 - try addToolButton(allocator, row, "✕", "", try std.fmt.allocPrint(pa, "a:{s}", .{try remove_route.toUrl(self.session.page_arena)})); │ │566 - } │ │561 + const edit_route = ui.RoutablePage.repoThreadEditRoute(kind, self.data.identity, entry.id) orelse return error.RouteTooLong; │ │562 + try addToolButton(allocator, row, "edit", "", try std.fmt.allocPrint(pa, "a:{s}", .{try edit_route.toUrl(self.session.page_arena)})); │ │563 + const remove_route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │564 + try addToolButton(allocator, row, "✕", "", try std.fmt.allocPrint(pa, "a:{s}", .{try remove_route.toUrl(self.session.page_arena)})); │ │565 } else { │ │568 - if (!self.session.is_terminal and !entryConflicted(entry) and (self.session.data.host_kind == .local or self.session.data.user_id != null)) {│ │566 + if (!self.session.is_terminal and !entryConflicted(entry)) { │ │567 const label = "add attachment"; │ │568 const action = if (self.data.identity.len == 0) │ │569 try std.fmt.allocPrint(pa, "{s}/{s}:{s}/attach", .{ ui.file_input_prefix, @tagName(kind), entry.id }) │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │576 const route = resolveRoute(self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │577 try addToolButton(allocator, row, "resolve conflict", "", try std.fmt.allocPrint(pa, "a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │578 } else { │ │581 - if (has_status and (self.session.data.host_kind == .local or self.session.data.user_id != null)) { │ │579 + if (has_status) { │ │580 if (statusChange(entryStatus(entry))) |change| { │ │581 const route = ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) orelse return error.RouteTooLong; │ │582 row.getFocus().kind = .{ .custom = try std.fmt.allocPrint(pa, "form:{s}/{s}", .{ try route.toUrl(self.session.page_arena), change.action }) };│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │588 try addToolButton(allocator, row, "edit", "", try std.fmt.allocPrint(pa, "a:{s}", .{try route.toUrl(self.session.page_arena)}));│ │589 } │ │590 │ │593 - if (self.session.data.host_kind == .local or self.session.data.user_id != null) { │ │594 - const route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │595 - try addToolButton(allocator, row, "✕", "", try std.fmt.allocPrint(pa, "a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │596 - } │ │591 + const route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │592 + try addToolButton(allocator, row, "✕", "", try std.fmt.allocPrint(pa, "a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │593 } │ │594 } │ │595 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1151 │ │1152 fn removeEvent(self: *This, allocator: std.mem.Allocator, event_kind: evt.EventKind, id: [evt.event_id_size]u8) !void {│ │1153 if (comptime wasm) return; │ │1154 + const actor = (try self.session.authorize(self.data.identity, .read)) orelse return; │ │1155 + const author = actor.author; │ │1156 │ │1157 if (comptime supports_drafts) { │ │1158 const entry = self.entry orelse return; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1159 if (event_kind == kind and entryDraft(entry)) { │ │1162 - try Data.removeDraft(self.session, allocator, &id); │ │1160 + try Data.removeDraft(self.session, allocator, author, &id); │ │1161 const route = draftsRoute(self.data.identity) orelse return;│ │1162 try self.session.navigate(route); │ │1163 return; │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │1166 │ │1167 const io = self.session.io orelse return; │ │1168 const source = self.data.repo_source orelse return; │ │1171 - const author = (try self.session.eventAuthor()) orelse return; │ │1169 + if (!try source.canModify(io, allocator, actor, event_kind, &id)) return;│ │1170 │ │1171 switch (source.repo_kind) { │ │1172 inline else => |repo_kind| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1188 const entry = self.entry orelse return; │ │1189 const io = self.session.io orelse return; │ │1190 const source = self.data.repo_source orelse return; │ │1193 - const author = (try self.session.eventAuthor()) orelse return; │ │1191 + const actor = (try self.session.authorize(self.data.identity, .read)) orelse return;│ │1192 + const author = actor.author; │ │1193 const status = (statusChange(entryStatus(entry)) orelse return).status; │ │1194 const id = try evt.parseEventId(entry.id); │ │1195 + if (!try source.canModify(io, allocator, actor, kind, &id)) return; │ │1196 │ │1197 switch (source.repo_kind) { │ │1198 inline else => |repo_kind| { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2380 const io = self.session.io orelse return; │ │2381 const src = self.data.repo_source orelse return; │ │2382 const entry = self.data.selectedThread() orelse return; │ │2383 - const author = (try self.session.eventAuthor()) orelse return; │ │2383 + const author = ((try self.session.authorize(self.data.identity, .write)) orelse return).author;│ │2384 const form = self.formBox() orelse return; │ │2385 │ │2386 // gather the inputs by name; the d<n> hunk inputs appear in chunk order │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2443 if (comptime wasm) return; │ │2444 const io = self.session.io orelse return; │ │2445 const src = self.data.repo_source orelse return; │ │2446 - const author = (try self.session.eventAuthor()) orelse return; │ │2446 + const actor = (try self.session.authorize(self.data.identity, .read)) orelse return;│ │2447 + const author = actor.author; │ │2448 const form = self.formBox() orelse return; │ │2449 const body_input = try formField(form, "body"); │ │2450 const body = try body_input.text(allocator); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2460 }; │ │2461 var event_id_hex: [evt.event_id_size * 2]u8 = undefined; │ │2462 const editing = self.data.view == .edit_comment; │ │2463 + if (editing) { │ │2464 + const comment_id = evt.parseEventId(self.data.comment_id) catch return; │ │2465 + if (!try src.canModify(io, allocator, actor, .comment, &comment_id)) return; │ │2466 + } │ │2467 switch (src.repo_kind) { │ │2468 inline else => |repo_kind| { │ │2469 var any_repo = try rp.AnyRepo(repo_kind, .{}).open(io, allocator, src.localInitOpts());│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯