╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │576 const open_view_index: usize = 0; │ │577 const closed_view_index: usize = 1; │ │578 const tags_view_index: usize = 2; │ │579 - // the new-issue form, or the edit or resolve form when the page was │ │579 + // the new-issue form, or the edit, comment, or resolve form when the page was│ │580 // loaded at one of their urls. │ │581 const form_view_index: usize = 3; │ │582 // the conflicts split; the tab and stack child exist only when the repo │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────╮ │591 const detail_index: usize = 1; │ │592 const list_max_width: usize = 40; │ │593 const detail_min_width: usize = 40; │ │594 - // indices within the new-issue form. │ │594 + // indices within the issue form. │ │595 const title_field_index: usize = 0; │ │596 const tags_field_index: usize = 1; │ │597 const description_field_index: usize = 2;│ ╰──────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────╮ │598 const submit_field_index: usize = 3; │ │599 + const comment_author_field_index: usize = 0; │ │600 + const comment_body_field_index: usize = 1; │ │601 + const comment_submit_field_index: usize = 2; │ │602 │ │603 fn viewIndex(view: ui.RoutablePage.IssuesView) usize {│ │604 return switch (view) { │ ╰───────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │605 .open => open_view_index, │ │606 .closed => closed_view_index, │ │607 .tags => tags_view_index, │ │605 - .new, .edit, .resolve => form_view_index, │ │608 + .new, .edit, .new_comment, .resolve => form_view_index, │ │609 .conflicts => conflicts_view_index, │ │610 // init resolves a description url to its issue's status list│ │611 .description => unreachable, │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │660 try stack.children.put(allocator, tf.getFocus().id, .{ .tag_flow = tf }); │ │661 } │ │662 │ │660 - // the new-issue form, or — on an edit or resolve url — that form in │ │663 + // the new-issue form, or — on an edit, comment, or resolve url — that form in│ │664 // its place, prefilled with the selected issue's content. a │ │665 // logged-out session can't create events, so the unauthorized view │ │666 // stands in. │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │667 if (session.data.is_local or session.data.user_id != null) { │ │665 - if (data.view == .resolve) { │ │668 + if (data.view == .new_comment) { │ │669 + const route = ui.RoutablePage.repoCommentNewRoute(data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong; │ │670 + const page_url = try route.toUrl(session.page_arena); │ │671 + const action = try std.fmt.allocPrint(session.page_arena.allocator(), "form:{s}/comment", .{page_url[0 .. page_url.len - "/new".len]}); │ │672 + const parent_route = if (data.comment_page) |page| │ │673 + ui.RoutablePage.repoCommentsRoute(data.identity, data.selected_id, page.selected.id, 0) │ │674 + else │ │675 + ui.RoutablePage.repoIssueCommentsRoute(data.identity, data.selected_id, 0); │ │676 + var form = try initCommentForm(allocator, session, action, if (data.comment_page) |page| page.selected.author else if (data.selectedIssue()) |entry| entry.author else .unknown, parent_route orelse return error.RouteTooLong);│ │677 + errdefer form.deinit(allocator); │ │678 + try stack.children.put(allocator, form.getFocus().id, .{ .box = form }); │ │679 + } else if (data.view == .resolve) { │ │680 // on the web the page grows to the form's height and the │ │681 // browser scrolls it; the terminal scrolls the form itself │ │682 var form_widget: ui.Widget = blk: { │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │853 return box; │ │854 } │ │855 │ │856 + fn initCommentForm(allocator: std.mem.Allocator, session: *ui.Session, action: []const u8, author: ui.Author, parent_route: ui.RoutablePage) !wgt.Box(ui.Widget) {│ │857 + var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │858 + errdefer box.deinit(allocator); │ │859 + box.getFocus().kind = .{ .custom = action }; │ │860 + │ │861 + { │ │862 + var parent = try ui.authorBox(allocator, session.page_arena, author); │ │863 + errdefer parent.deinit(allocator); │ │864 + parent.options.label = " replying to "; │ │865 + parent.getFocus().kind = .{ .custom = try std.fmt.allocPrint(session.page_arena.allocator(), "a:{s}", .{try parent_route.toUrl(session.page_arena)}) }; │ │866 + try box.children.put(allocator, parent.getFocus().id, .{ .widget = .{ .text_box = parent }, .rect = null, .min_size = null }); │ │867 + } │ │868 + │ │869 + { │ │870 + var body = try wgt.TextInput(ui.Widget).init(allocator, .{ │ │871 + .label = " comment ", │ │872 + .name = "body", │ │873 + .visible_width = null, │ │874 + .rounded_corners = true, │ │875 + .render_content = session.is_terminal, │ │876 + .multiline = true, │ │877 + .visible_height = 5, │ │878 + }); │ │879 + errdefer body.deinit(allocator); │ │880 + body.getFocus().focusable = true; │ │881 + try box.children.put(allocator, body.getFocus().id, .{ .widget = .{ .text_input = body }, .rect = null, .min_size = null }); │ │882 + } │ │883 + │ │884 + try addSubmitButtonLabeled(allocator, &box, "submit reply"); │ │885 + box.getFocus().child_id = box.children.keys()[comment_author_field_index]; │ │886 + return box; │ │887 + } │ │888 + │ │889 // a form's submit button, then a spacer absorbing the leftover │ │890 // min-height the box hands its last child, so the button keeps its │ │891 // natural height │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │892 fn addSubmitButton(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget)) !void { │ │893 + return addSubmitButtonLabeled(allocator, box, "submit"); │ │894 + } │ │895 + │ │896 + fn addSubmitButtonLabeled(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), label: []const u8) !void { │ │897 { │ │847 - var submit = try ui.SubmitButton.init(allocator); │ │898 + var submit = try ui.SubmitButton.initLabeled(allocator, label); │ │899 errdefer submit.deinit(allocator); │ │900 try box.children.put(allocator, submit.getFocus().id, .{ .widget = .{ .submit_button = submit }, .rect = null, .min_size = .{ .width = null, .height = 3 } });│ │901 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1236 self.session.data.current_page = (if (index == form_view_index) │ │1237 (switch (self.data.view) { │ │1238 .edit => ui.RoutablePage.repoIssuesEditRoute(self.data.identity, self.data.selected_id), │ │1239 + .new_comment => ui.RoutablePage.repoCommentNewRoute(self.data.identity, self.data.selected_id, self.data.comment_id), │ │1240 .resolve => ui.RoutablePage.repoIssuesResolveRoute(self.data.identity, self.data.selected_id, self.data.theirs_picks),│ │1241 else => ui.RoutablePage.repoIssuesNewRoute(self.data.identity), │ │1242 }) │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1530 }; │ │1531 │ │1532 if (!description_page) { │ │1533 + var reply = try Comment.linkBox(allocator, self.session, "new comment", ui.RoutablePage.repoCommentNewRoute(self.data.identity, entry.id, "") orelse return error.RouteTooLong);│ │1534 + errdefer reply.deinit(allocator); │ │1535 + try inner.children.put(allocator, reply.getFocus().id, .{ .widget = .{ .text_box = reply }, .rect = null, .min_size = null }); │ │1536 + │ │1537 try Comment.appendCount(allocator, inner, entry.comments.count, "comment", "comments", self.session.page_arena.allocator()); │ │1538 for (entry.comments.comments) |comment| try Comment.appendComment(allocator, inner, self.session, self.data.identity, comment); │ │1539 try Comment.appendWindowNav(allocator, inner, self.session, self.data.identity, entry.id, null, entry.comments); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯