╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────╮ │573 description_id: [stack_child_max]?usize, │ │574 author_id: [stack_child_max]?usize, │ │575 status_button_id: [stack_child_max]?usize,│ │576 + remove_button_id: [stack_child_max]?usize,│ │577 │ │578 const header_index: usize = 0; │ │579 const stack_index: usize = 1; │ ╰───────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │765 .description_id = @splat(null), │ │766 .author_id = @splat(null), │ │767 .status_button_id = @splat(null),│ │768 + .remove_button_id = @splat(null),│ │769 }; │ │770 } │ │771 │ ╰──────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │1410 inner.getFocus().child_id = null; │ │1411 self.author_id[index] = null; │ │1412 self.status_button_id[index] = null; │ │1413 + self.remove_button_id[index] = null; │ │1414 │ │1415 // the tool row: the open/close and edit buttons; the description page│ │1416 // shows none. │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1492 try row.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = .{ .width = "edit".len + 2, .height = null } });│ │1493 } │ │1494 } │ │1495 + │ │1496 + if (self.session.data.is_local or self.session.data.user_id != null) { │ │1497 + const issue_route = ui.RoutablePage.repoIssueCommentsRoute(self.data.identity, entry.id, 0) orelse return error.RouteTooLong; │ │1498 + const remove_url = try std.fmt.allocPrint(pa, "{s}/remove", .{try issue_route.toUrl(self.session.page_arena)}); │ │1499 + const kind: []const u8 = if (entry.conflicted) blk: { │ │1500 + row.getFocus().kind = .{ .custom = try std.fmt.allocPrint(pa, "form:{s}", .{remove_url}) }; │ │1501 + break :blk "submit"; │ │1502 + } else try std.fmt.allocPrint(pa, "{s}{s}", .{ ui.submit_action_prefix, remove_url }); │ │1503 │ │1504 + var remove = try wgt.TextBox(ui.Widget).init(allocator, "✕", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │1505 + errdefer remove.deinit(allocator); │ │1506 + remove.getFocus().focusable = true; │ │1507 + remove.getFocus().kind = .{ .custom = kind }; │ │1508 + self.remove_button_id[index] = remove.getFocus().id; │ │1509 + try row.children.put(allocator, remove.getFocus().id, .{ .widget = .{ .text_box = remove }, .rect = null, .min_size = .{ .width = 3, .height = null } }); │ │1510 + } │ │1511 + │ │1512 // the leftmost button: the attachment button when the session has │ │1513 // one, else the resolve button on a conflicted issue, the │ │1514 // open/close button when present, the edit button otherwise │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1606 break :blk tb.getFocus().id; │ │1607 }; │ │1608 │ │1590 - try Attachment.appendRows(allocator, inner, self.session, self.data.identity, entry.attachments); │ │1609 + const parent_route = ui.RoutablePage.repoIssueCommentsRoute(self.data.identity, entry.id, 0) orelse return error.RouteTooLong; │ │1610 + try Attachment.appendRows(allocator, inner, self.session, self.data.identity, try parent_route.toUrl(self.session.page_arena), entry.attachments); │ │1611 │ │1612 if (!description_page) { │ │1613 var reply = try Comment.linkBox(allocator, self.session, "new comment", ui.RoutablePage.repoCommentNewRoute(self.data.identity, entry.id, "") orelse return error.RouteTooLong);│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1736 else => {}, │ │1737 } │ │1738 if (self.data.comment_page != null and std.mem.eql(u8, self.window(index).issues[self.detailed_index[index] orelse return].id, self.data.selected_id)) {│ │1719 - try self.commentInput(index, key, root_focus); │ │1739 + try self.commentInput(allocator, index, key, root_focus); │ │1740 } else if (self.toolRowFocused(index)) { │ │1741 try self.toolRowInput(allocator, index, key, root_focus); │ │1742 } else if (self.titleFocused(index)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │1747 try self.tagsInput(index, key, root_focus); │ │1748 } else if (self.descriptionFocused(index)) { │ │1749 try self.descriptionInput(index, key, root_focus); │ │1730 - } else { │ │1731 - try self.commentInput(index, key, root_focus); │ │1750 + } else if (try self.attachmentInput(allocator, index, key, root_focus)) {} else {│ │1751 + try self.commentInput(allocator, index, key, root_focus); │ │1752 } │ │1753 } │ │1754 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1755 + fn attachmentInput(self: *View, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !bool { │ │1756 + const child_index = self.focusedDetailChild(index, root_focus) orelse return false; │ │1757 + const child = &self.detailInner(index).children.values()[child_index].widget; │ │1758 + const row = switch (child.*) { │ │1759 + .box => |*box| box, │ │1760 + else => return false, │ │1761 + }; │ │1762 + const attachment_id = Attachment.removeId(row) orelse return false; │ │1763 + if (row.children.count() < 2) return false; │ │1764 + const remove_id = row.children.keys()[1]; │ │1765 + switch (key) { │ │1766 + .arrow_left => if (!self.moveCommentHorizontal(index, root_focus, false)) try self.focusList(index, root_focus), │ │1767 + .arrow_right => _ = self.moveCommentHorizontal(index, root_focus, true), │ │1768 + .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1769 + .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1770 + .enter => if (row.getFocus().child_id == remove_id) try self.removeEvent(allocator, .attach, attachment_id), │ │1771 + .mouse => |mouse| if (inp.leftClickOn(root_focus, remove_id, mouse)) try self.removeEvent(allocator, .attach, attachment_id),│ │1772 + else => {}, │ │1773 + } │ │1774 + return true; │ │1775 + } │ │1776 + │ │1777 // the tool row: enter or a click on the open/close button flips the │ │1778 // issue's status; the edit and resolve buttons are a: links the host │ │1779 // follows. arrows cross between the buttons and to the neighboring │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1782 const row = self.toolRow(index); │ │1783 const cur = if (row.getFocus().child_id) |cid| row.children.getIndex(cid) orelse return else return; │ │1784 const on_status = if (self.status_button_id[index]) |id| row.getFocus().child_id == id else false; │ │1785 + const on_remove = if (self.remove_button_id[index]) |id| row.getFocus().child_id == id else false; │ │1786 switch (key) { │ │1787 .arrow_left => if (cur > first_in_row_index) root_focus.setFocus(row.children.keys()[cur - 1]) else try self.focusList(index, root_focus),│ │1788 .arrow_right => if (cur + 1 < row.children.count()) root_focus.setFocus(row.children.keys()[cur + 1]), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1791 self.focusDetailEdge(index, root_focus, false); │ │1792 self.detailScroll(index).y = 0; │ │1793 }, │ │1751 - .enter => if (on_status) try self.toggleIssueStatus(allocator, index), │ │1752 - .mouse => |mouse| if (self.status_button_id[index]) |id| { │ │1794 + .enter => if (on_remove) try self.removeIssue(allocator, index) else if (on_status) try self.toggleIssueStatus(allocator, index), │ │1795 + .mouse => |mouse| if (self.remove_button_id[index]) |id| { │ │1796 + if (inp.leftClickOn(root_focus, id, mouse)) return self.removeIssue(allocator, index); │ │1797 + if (self.status_button_id[index]) |status_id| if (inp.leftClickOn(root_focus, status_id, mouse)) try self.toggleIssueStatus(allocator, index);│ │1798 + } else if (self.status_button_id[index]) |id| { │ │1799 if (inp.leftClickOn(root_focus, id, mouse)) try self.toggleIssueStatus(allocator, index); │ │1800 }, │ │1801 else => {}, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯