╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1767 if (word) |tail| { │ │1768 if (std.mem.eql(u8, tail, "new")) return if (params.only(&.{})) repoThreadCommentNewRoute(.discuss, pair, discussion_id, "") else null; │ │1769 if (std.mem.eql(u8, tail, "edit")) return if (params.only(&.{})) repoThreadEditRoute(.discuss, pair, discussion_id) else null; │ │1753 - if (std.mem.eql(u8, tail, "remove")) return if (params.only(&.{})) repoThreadRemoveRoute(.discuss, pair, discussion_id, "") else null; │ │1770 + if (std.mem.eql(u8, tail, "remove")) return if (params.only(&.{.attachment})) repoThreadRemoveRoute(.discuss, pair, discussion_id, "", params.values.get(.attachment) orelse "") else null;│ │1771 if (std.mem.eql(u8, tail, "description")) return if (params.only(&.{})) repoThreadDescriptionRoute(.discuss, pair, discussion_id) else null; │ │1772 return null; │ │1773 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────────╮ │src/ui/Repo/Attachment.zig│ ╰──────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │65 tb.getFocus().kind = .{ .custom = try pa.print("{s}{s}", .{ ui.raw_link_prefix, try url(session.page_arena, identity, &entry.id) }) };│ │66 try row.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │67 │ │68 - row.getFocus().kind = .{ .custom = try pa.print("form:{s}/attachment:{s}/remove", .{ target_url, &entry.id }) }; │ │68 + // the ✕ opens a page confirming the removal │ │69 var remove = try wgt.TextBox.init(allocator, "✕", .{ .border = .single, .round_corners = true, .wrap_kind = .none }); │ │70 errdefer remove.deinit(allocator); │ │71 remove.getFocus().mode = .all; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │72 - remove.getFocus().kind = .{ .custom = "submit" }; │ │72 + remove.getFocus().kind = .{ .custom = try pa.print("a:{s}/attachment:{s}/remove", .{ target_url, &entry.id }) }; │ │73 try row.children.put(allocator, remove.getFocus().id, .{ .widget = .{ .text_box = remove }, .rect = null, .min_size = .{ .width = 3, .height = null } });│ │74 │ │75 row.getFocus().child_id = row.children.keys()[0]; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │77 } │ │78 } │ │79 │ │80 -// the attachment id carried by a removable row │ │81 -pub fn removeId(row: *wgt.Box(ui.Widget)) ?[evt.event_id_size]u8 { │ │82 - const prefix = "form:"; │ │83 - const infix = "/attachment:"; │ │84 - const suffix = "/remove"; │ │85 - const action = switch (row.getFocus().kind) { │ │80 +// whether a row is an attachment's, by the remove link on its ✕ │ │81 +pub fn isRow(row: *wgt.Box(ui.Widget)) bool { │ │82 + const last = row.children.values()[row.children.count() - 1].widget; │ │83 + if (last != .text_box) return false; │ │84 + const link = switch (last.text_box.focus.kind) { │ │85 .custom => |custom| custom, │ │87 - else => return null, │ │86 + else => return false, │ │87 }; │ │89 - if (!std.mem.startsWith(u8, action, prefix) or !std.mem.endsWith(u8, action, suffix)) return null; │ │90 - const at = std.mem.lastIndexOf(u8, action, infix) orelse return null; │ │91 - const id = action[at + infix.len .. action.len - suffix.len]; │ │92 - if (id.len != evt.event_id_size * 2) return null; │ │93 - var bytes: [evt.event_id_size]u8 = undefined; │ │94 - _ = std.fmt.hexToBytes(&bytes, id) catch return null; │ │95 - return bytes; │ │88 + return std.mem.startsWith(u8, link, "a:") and std.mem.indexOf(u8, link, "/attachment:") != null and std.mem.endsWith(u8, link, "/remove");│ │89 } │ │90 │ │91 // the url an attachment's bytes are served from │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Comment.zig│ ╰───────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │352 } │ │353 │ │354 fn removeRoute(kind: evt.EventKind, identity: []const u8, thread_id: []const u8, comment_id: []const u8) ?ui.RoutablePage {│ │355 - return ui.RoutablePage.repoThreadRemoveRoute(kind, identity, thread_id, comment_id); │ │355 + return ui.RoutablePage.repoThreadRemoveRoute(kind, identity, thread_id, comment_id, ""); │ │356 } │ │357 │ │358 pub fn linkBox(allocator: std.mem.Allocator, session: *ui.Session, text: []const u8, route: ui.RoutablePage) !wgt.TextBox {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────────╮ │src/ui/Repo/Discussions.zig│ ╰───────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │255 .edit => ui.RoutablePage.repoThreadEditRoute(.discuss, data.identity, data.selected_id) orelse return error.RouteTooLong, │ │256 .new_comment => ui.RoutablePage.repoThreadCommentNewRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │257 .edit_comment => ui.RoutablePage.repoThreadCommentEditRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong,│ │258 - .remove => ui.RoutablePage.repoThreadRemoveRoute(.discuss, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │258 + .remove => ui.RoutablePage.repoThreadRemoveRoute(.discuss, data.identity, data.selected_id, data.comment_id, "") orelse return error.RouteTooLong, │ │259 else => ui.RoutablePage.repoThreadNewRoute(.discuss, data.identity) orelse return error.RouteTooLong, │ │260 }; │ │261 const link = try ui.inPageTabLink(session, route, page_selected and selected_index == View.viewIndex(.new)); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │414 .edit => ui.RoutablePage.repoThreadEditRoute(.issue, data.identity, data.selected_id) orelse return error.RouteTooLong, │ │415 .new_comment => ui.RoutablePage.repoThreadCommentNewRoute(.issue, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │416 .edit_comment => ui.RoutablePage.repoThreadCommentEditRoute(.issue, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong,│ │417 - .remove => ui.RoutablePage.repoThreadRemoveRoute(.issue, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │417 + .remove => ui.RoutablePage.repoThreadRemoveRoute(.issue, data.identity, data.selected_id, data.comment_id, "") orelse return error.RouteTooLong, │ │418 .resolve => ui.RoutablePage.repoIssuesResolveRoute(data.identity, data.selected_id, data.theirs_picks) orelse return error.RouteTooLong, │ │419 else => ui.RoutablePage.repoThreadNewRoute(.issue, data.identity) orelse return error.RouteTooLong, │ │420 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Patches.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1021 .merge => ui.RoutablePage.repoPatchMergeRoute(data.identity, data.selected_id) orelse return error.RouteTooLong, │ │1022 .new_comment => ui.RoutablePage.repoThreadCommentNewRoute(.patch, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │1023 .edit_comment => ui.RoutablePage.repoThreadCommentEditRoute(.patch, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong,│ │1024 - .remove => ui.RoutablePage.repoThreadRemoveRoute(.patch, data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │1024 + .remove => ui.RoutablePage.repoThreadRemoveRoute(.patch, data.identity, data.selected_id, data.comment_id, "") orelse return error.RouteTooLong, │ │1025 .resolve => ui.RoutablePage.repoPatchesResolveRoute(data.identity, data.selected_id, data.theirs_picks) orelse return error.RouteTooLong, │ │1026 else => ui.RoutablePage.repoThreadNewRoute(.patch, data.identity) orelse return error.RouteTooLong, │ │1027 }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │933 try addToolButton(allocator, row, "publish", "", try pa.print("a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │934 const edit_route = ui.RoutablePage.repoThreadEditRoute(kind, self.data.identity, entry.id) orelse return error.RouteTooLong; │ │935 try addToolButton(allocator, row, "edit", "", try pa.print("a:{s}", .{try edit_route.toUrl(self.session.page_arena)})); │ │936 - const remove_route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │936 + const remove_route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "", "") orelse return error.RouteTooLong;│ │937 try addToolButton(allocator, row, "✕", "", try pa.print("a:{s}", .{try remove_route.toUrl(self.session.page_arena)})); │ │938 } else { │ │939 if (!self.session.is_terminal and !entryConflicted(entry)) { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │974 try addToolButton(allocator, row, "edit", "", try pa.print("a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │975 } │ │976 │ │977 - const route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "") orelse return error.RouteTooLong; │ │977 + const route = ui.RoutablePage.repoThreadRemoveRoute(kind, self.data.identity, entry.id, "", "") orelse return error.RouteTooLong;│ │978 try addToolButton(allocator, row, "✕", "", try pa.print("a:{s}", .{try route.toUrl(self.session.page_arena)})); │ │979 } │ │980 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯