╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────────────────────────────────────────╮ │2 const builtin = @import("builtin"); │ │3 const pch = @import("../../patch.zig"); │ │4 const evt = @import("../../event.zig"); │ │5 +const notify = @import("../../notify.zig");│ │6 const ui = @import("../../ui.zig"); │ │7 const widget = @import("../widget.zig"); │ │8 const xit = @import("xit"); │ ╰──────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │932 try addToolButton(allocator, row, label, "", action); │ │933 } │ │934 │ │934 - if (supports_conflicts and entryConflicted(entry)) { │ │935 + const conflicted = supports_conflicts and entryConflicted(entry); │ │936 + if (conflicted) { │ │937 const route = resolveRoute(self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │938 try addToolButton(allocator, row, "resolve conflict", "", try pa.print("a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │937 - } else { │ │938 - if (has_status) { │ │939 - if (statusChange(entryStatus(entry))) |change| { │ │940 - const route = ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) orelse return error.RouteTooLong; │ │941 - row.getFocus().kind = .{ .custom = try pa.print("form:{s}/{s}", .{ try route.toUrl(self.session.page_arena), change.action }) };│ │942 - try addToolButton(allocator, row, change.action, "", "submit"); │ │943 - } │ │939 + } else if (has_status) { │ │940 + if (statusChange(entryStatus(entry))) |change| { │ │941 + const route = ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) orelse return error.RouteTooLong; │ │942 + row.getFocus().kind = .{ .custom = try pa.print("form:{s}/{s}", .{ try route.toUrl(self.session.page_arena), change.action }) }; │ │943 + try addToolButton(allocator, row, change.action, "", "submit"); │ │944 } │ │945 + } │ │946 │ │947 + if (entry.notify_action) |mode| { │ │948 + const route = ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) orelse return error.RouteTooLong; │ │949 + const action = try pa.print("{s}/{s}", .{ try route.toUrl(self.session.page_arena), @tagName(mode) }); │ │950 + // a status change already made the row a form, which the button's own action overrides │ │951 + if (row.getFocus().kind != .custom) row.getFocus().kind = .{ .custom = try pa.print("form:{s}", .{action}) }; │ │952 + try addToolButton(allocator, row, @tagName(mode), "", try pa.print("{s}{s}", .{ ui.submit_action_prefix, action })); │ │953 + } │ │954 + │ │955 + if (!conflicted) { │ │956 const route = ui.RoutablePage.repoThreadEditRoute(kind, self.data.identity, entry.id) orelse return error.RouteTooLong; │ │957 try addToolButton(allocator, row, "edit", "", try pa.print("a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │958 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1204 const row = self.toolRow(); │ │1205 const child_id = row.getFocus().child_id orelse return; │ │1206 const cur = row.children.getIndex(child_id) orelse return; │ │1197 - const on_primary = switch (row.children.values()[cur].widget.getFocus().kind) { │ │1198 - .custom => |action| std.mem.eql(u8, action, "submit"), │ │1199 - else => false, │ │1207 + const action: []const u8 = switch (row.children.values()[cur].widget.getFocus().kind) {│ │1208 + .custom => |custom| custom, │ │1209 + else => "", │ │1210 }; │ │1211 + const on_primary = std.mem.eql(u8, action, "submit"); │ │1212 + // the notify button is the row's only one with its own action │ │1213 + const on_notify = std.mem.startsWith(u8, action, ui.submit_action_prefix); │ │1214 switch (key) { │ │1215 .arrow_left => { │ │1216 var previous = cur; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1232 }, │ │1233 .arrow_up => self.exit = .header, │ │1234 .arrow_down => _ = self.moveVertical(root_focus, true), │ │1222 - .enter => if (on_primary) try self.primaryAction(allocator), │ │1223 - .mouse => |mouse| if (on_primary and inp.leftClickOn(root_focus, child_id, mouse)) try self.primaryAction(allocator),│ │1235 + .enter => if (on_primary) { │ │1236 + try self.primaryAction(allocator); │ │1237 + } else if (on_notify) try self.changeNotify(allocator), │ │1238 + .mouse => |mouse| if (inp.leftClickOn(root_focus, child_id, mouse)) { │ │1239 + if (on_primary) { │ │1240 + try self.primaryAction(allocator); │ │1241 + } else if (on_notify) try self.changeNotify(allocator); │ │1242 + }, │ │1243 else => {}, │ │1244 } │ │1245 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1680 }, │ │1681 } │ │1682 }, │ │1683 + } │ │1684 + try self.session.navigate(ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) orelse return error.RouteTooLong);│ │1685 + } │ │1686 + │ │1687 + // subscribe to the thread or mute it, as its button says │ │1688 + fn changeNotify(self: *This, allocator: std.mem.Allocator) !void { │ │1689 + if (comptime wasm) return; │ │1690 + const entry = self.entry orelse return; │ │1691 + const mode = entry.notify_action orelse return; │ │1692 + const io = self.session.io orelse return; │ │1693 + const users_dir = self.session.users_dir orelse return; │ │1694 + const source = self.data.repo_source orelse return; │ │1695 + const actor = (try self.session.authorize(self.data.identity, .read, null)) orelse return; │ │1696 + // a server actor always has both ids │ │1697 + const user_id = actor.user_id orelse unreachable; │ │1698 + const location: evt.RepoLocation = .{ │ │1699 + .owner_id = actor.repo_user_id orelse unreachable, │ │1700 + .repo_id = try evt.parseEventId(std.Io.Dir.path.basename(source.path)), │ │1701 + }; │ │1702 + const thread_id = try evt.parseEventId(entry.id); │ │1703 + switch (mode) { │ │1704 + .subscribe => try notify.subscribe(io, allocator, users_dir, &user_id, location, &thread_id, actor.author), │ │1705 + .mute => try notify.mute(io, allocator, users_dir, &user_id, location, &thread_id, actor.author), │ │1706 } │ │1707 try self.session.navigate(ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, entry.id, 0) orelse return error.RouteTooLong);│ │1708 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯