╭ changed ───────────╮ │src/ui/Repo/Undo.zig│ ╰────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────╮ │ 2 const builtin = @import("builtin"); │ │ 3 const ui = @import("../../ui.zig"); │ │ 4 const evt = @import("../../event.zig");│ │ 5 +const pch = @import("../../patch.zig");│ │ 6 +const push = @import("../../push.zig");│ │ 7 +const fork = @import("../../fork.zig");│ │ 8 const inp = @import("../input.zig"); │ │ 9 const xit = @import("xit"); │ │10 const rp = xit.repo; │ ╰───────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │18 │ │19 pub const page_size = 20; │ │20 │ │18 -// replaces the description for a transaction that wrote events │ │19 -pub const EventsLink = struct { │ │21 +// stands in for the description, as a focusable box that may link somewhere│ │22 +pub const DetailButton = struct { │ │23 label: []const u8, │ │21 - link: []const u8, │ │24 + text: []const u8, │ │25 + link: ?[]const u8 = null, │ │26 }; │ │27 │ │28 pub const Item = struct { │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────╮ │32 timestamp: []const u8, │ │33 link: []const u8, │ │34 form: []const u8, │ │31 - events: ?EventsLink = null, │ │35 + button: ?DetailButton = null,│ │36 }; │ │37 │ │38 identity: []const u8, │ ╰─────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │44 // the clear-history confirmation stands in for the list │ │45 clear: bool = false, │ │46 │ │43 -pub fn init(comptime opts: rp.RepoOpts(.xit), arena: *std.heap.ArenaAllocator, repo: *rp.Repo(.xit, opts), identity: []const u8, selected: ?u64) !Self { │ │47 +pub fn init(comptime opts: rp.RepoOpts(.xit), arena: *std.heap.ArenaAllocator, repo: *rp.Repo(.xit, opts), haxy_moment: ?evt.AdminDB.HashMap(.read_only), identity: []const u8, selected: ?u64) !Self {│ │48 const aa = arena.allocator(); │ │49 const DB = rp.Repo(.xit, opts).DB; │ │50 const history = try DB.ArrayList(.read_only).init(repo.core.db.rootCursor().readOnly()); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │59 const moment = try DB.HashMap(.read_only).init(cursor); │ │60 const record = try xit.undo.read(opts, moment, &buffer); │ │61 var description: std.Io.Writer.Allocating = .init(aa); │ │58 - const detail = if (record) |value| try eventDetail(opts, arena, identity, value, moment, remaining) else null; │ │62 + const detail = if (record) |value| try eventDetail(opts, arena, haxy_moment, identity, value, moment, remaining) else null;│ │63 if (detail) |value| { │ │64 try description.writer.writeAll(value.description); │ │65 } else if (record) |value| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │73 .index = remaining, │ │74 .action = if (detail) |value| value.action else if (record) |value| try aa.dupe(u8, value.action) else "unknown",│ │75 .description = description.written(), │ │72 - .events = if (detail) |value| value.events else null, │ │76 + .button = if (detail) |value| value.button else null, │ │77 .timestamp = if (record) |value| try formatTimestamp(aa, value.timestamp) else "timestamp unavailable", │ │78 .link = try std.fmt.allocPrint(aa, "ai:{s}", .{url}), │ │79 .form = try std.fmt.allocPrint(aa, "form:{s}/undo", .{url}), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │85 const Detail = struct { │ │86 action: []const u8, │ │87 description: []const u8, │ │84 - events: ?EventsLink = null, │ │88 + button: ?DetailButton = null, │ │89 }; │ │90 │ │91 // every haxy event transaction shares one action, and the events it wrote are│ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │93 fn eventDetail( │ │94 comptime opts: rp.RepoOpts(.xit), │ │95 arena: *std.heap.ArenaAllocator, │ │96 + haxy_moment: ?evt.AdminDB.HashMap(.read_only), │ │97 identity: []const u8, │ │98 record: xit.undo.UndoRecord, │ │99 moment: rp.Repo(.xit, opts).DB.HashMap(.read_only),│ ╰───────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │100 history_index: u64, │ │101 ) !?Detail { │ │102 if (std.mem.eql(u8, record.action, evt.merge_undo_action)) return .{ .action = "merge events", .description = "merged the events ref from a remote" }; │ │103 + if (std.mem.eql(u8, record.action, pch.merge_undo_action)) return .{ .action = "merge patch", .description = "merged a patch into its target branch" };│ │104 + if (std.mem.eql(u8, record.action, fork.undo_action)) return .{ .action = "fork", .description = "created a fork to draft a patch in" }; │ │105 + if (std.mem.eql(u8, record.action, push.undo_action)) return .{ │ │106 + .action = "push", │ │107 + .description = "received a push", │ │108 + .button = try pushUser(arena, haxy_moment, record.payload), │ │109 + }; │ │110 if (!std.mem.eql(u8, record.action, evt.undo_action)) return null; │ │111 │ │112 const count = try momentEventCount(opts, moment) orelse return .{ .action = "events", .description = "updated the event database" }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │120 return .{ │ │121 .action = try std.fmt.allocPrint(aa, "events ({d})", .{count}), │ │122 .description = "updated the event database", │ │111 - .events = .{ │ │112 - .label = try std.fmt.allocPrint(aa, "view events ({d})", .{count}), │ │123 + .button = .{ │ │124 + .label = " events ", │ │125 + .text = try std.fmt.allocPrint(aa, "view events ({d})", .{count}), │ │126 .link = try std.fmt.allocPrint(aa, "a:{s}", .{try route.toUrl(arena)}), │ │127 }, │ │128 + }; │ │129 +} │ │130 + │ │131 +// the push record stores the email of the user whose key the server accepted. │ │132 +// resolving it names them as they are now, and leaves the email when no user │ │133 +// holds it, which is also what local mode sees with no admin db to read │ │134 +fn pushUser(arena: *std.heap.ArenaAllocator, haxy_moment: ?evt.AdminDB.HashMap(.read_only), payload: []const u8) !?DetailButton { │ │135 + const Payload = struct { author: ?struct { email: []const u8 } = null }; │ │136 + const parsed = std.json.parseFromSlice(Payload, arena.child_allocator, payload, .{ .ignore_unknown_fields = true }) catch |err| switch (err) {│ │137 + error.OutOfMemory => return err, │ │138 + else => return null, │ │139 + }; │ │140 + defer parsed.deinit(); │ │141 + const author = parsed.value.author orelse return null; │ │142 + return switch (try ui.Author.initFromEmail(haxy_moment, arena, author.email)) { │ │143 + .unknown => null, │ │144 + .email => |email| .{ .label = " user ", .text = email }, │ │145 + .user_name => |name| .{ .label = " user ", .text = name, .link = try ui.userLink(arena, name) }, │ │146 }; │ │147 } │ │148 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯