╭ changed ─────────────╮ │src/ui/Repo/Issues.zig│ ╰──────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │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); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────╮ │1585 if (self.formViewActive()) { │ │1586 if (self.data.view == .resolve) { │ │1587 try self.resolveInput(allocator, key, root_focus); │ │1588 + } else if (self.data.view == .new_comment) { │ │1589 + try self.commentFormInput(allocator, key, root_focus);│ │1590 } else { │ │1591 try self.formInput(allocator, key, root_focus); │ │1592 } │ ╰────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1874 if (inp.leftClickOn(root_focus, submit.buttonId(), mouse)) try self.submitForm(allocator); │ │1875 }, │ │1876 else => try form.children.values()[cur].widget.input(allocator, key, root_focus), │ │1877 + } │ │1878 + } │ │1879 + │ │1880 + fn commentFormInput(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │1881 + const form = self.issueForm() orelse return; │ │1882 + const cid = form.getFocus().child_id orelse return; │ │1883 + const cur = form.children.getIndex(cid) orelse return; │ │1884 + const child = &form.children.values()[cur]; │ │1885 + │ │1886 + if (cur == comment_body_field_index and try multilineKeepsKey(&child.widget.text_input, allocator, key))│ │1887 + return child.widget.input(allocator, key, root_focus); │ │1888 + │ │1889 + switch (key) { │ │1890 + .arrow_up, .back_tab => if (cur > comment_author_field_index) │ │1891 + root_focus.setFocus(form.children.keys()[cur - 1]) │ │1892 + else │ │1893 + self.focusHeader(root_focus), │ │1894 + .arrow_down, .tab => if (cur < comment_submit_field_index) │ │1895 + root_focus.setFocus(form.children.keys()[cur + 1]), │ │1896 + .enter => if (cur == comment_submit_field_index) try self.submitComment(allocator), │ │1897 + .mouse => |mouse| if (cur == comment_submit_field_index) { │ │1898 + const submit = &child.widget.submit_button; │ │1899 + if (inp.leftClickOn(root_focus, submit.buttonId(), mouse)) try self.submitComment(allocator); │ │1900 + }, │ │1901 + else => try child.widget.input(allocator, key, root_focus), │ │1902 } │ │1903 } │ │1904 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2047 try self.session.navigate(route); │ │2048 } │ │2049 │ │2050 + // commit a reply and navigate to its permalink. this is the terminal path; │ │2051 + // the web posts the form to its /comment route. │ │2052 + fn submitComment(self: *View, allocator: std.mem.Allocator) !void { │ │2053 + if (comptime wasm) return; │ │2054 + const io = self.session.io orelse return; │ │2055 + const src = self.data.repo_source orelse return; │ │2056 + const author_email = (try self.session.eventAuthorEmail()) orelse return; │ │2057 + const form = self.issueForm() orelse return; │ │2058 + const body_input = &form.children.values()[comment_body_field_index].widget.text_input; │ │2059 + const body = try body_input.text(allocator); │ │2060 + defer allocator.free(body); │ │2061 + if (!evt.Comment.fieldsValid(body)) return; │ │2062 + │ │2063 + var id_bytes: [evt.event_id_size]u8 = undefined; │ │2064 + io.random(&id_bytes); │ │2065 + const event_id_hex = std.fmt.bytesToHex(id_bytes, .lower); │ │2066 + var thread_id: [evt.event_id_size * 2]u8 = undefined; │ │2067 + @memcpy(&thread_id, self.data.selected_id); │ │2068 + const parent_id = if (self.data.comment_id.len == 0) thread_id else blk: { │ │2069 + var parent: [evt.event_id_size * 2]u8 = undefined; │ │2070 + @memcpy(&parent, self.data.comment_id); │ │2071 + break :blk parent; │ │2072 + }; │ │2073 + const event = evt.EventWithId{ │ │2074 + .id = event_id_hex, │ │2075 + .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │2076 + .author_email = author_email, │ │2077 + .event = .{ .comment = .{ │ │2078 + .thread_id = thread_id, │ │2079 + .parent_id = parent_id, │ │2080 + .body = body, │ │2081 + } }, │ │2082 + }; │ │2083 + │ │2084 + switch (src.repo_kind) { │ │2085 + inline else => |repo_kind| { │ │2086 + var any_repo = try rp.AnyRepo(repo_kind, .{}).open(io, allocator, .{ .path = src.path }); │ │2087 + defer any_repo.deinit(io, allocator); │ │2088 + switch (any_repo) { │ │2089 + inline else => |*repo| try evt.consume(repo_kind, repo.self_repo_opts, io, allocator, repo, evt.events_ref, &.{event}),│ │2090 + } │ │2091 + }, │ │2092 + } │ │2093 + │ │2094 + body_input.clear(allocator); │ │2095 + const route = ui.RoutablePage.repoCommentsRoute(self.data.identity, &thread_id, &event_id_hex, 0) orelse return; │ │2096 + try self.session.navigate(route); │ │2097 + } │ │2098 + │ │2099 // the terminal submit paths: the new form commits a new-issue event, the │ │2100 // edit form re-emits the selected issue's event with the form's content. │ │2101 // the web posts the forms to their routes instead. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2492 { │ │2493 const route = switch (data.view) { │ │2494 .edit => ui.RoutablePage.repoIssuesEditRoute(data.identity, data.selected_id) orelse return error.RouteTooLong, │ │2495 + .new_comment => ui.RoutablePage.repoCommentNewRoute(data.identity, data.selected_id, data.comment_id) orelse return error.RouteTooLong, │ │2496 .resolve => ui.RoutablePage.repoIssuesResolveRoute(data.identity, data.selected_id, data.theirs_picks) orelse return error.RouteTooLong,│ │2497 else => ui.RoutablePage.repoIssuesNewRoute(data.identity) orelse return error.RouteTooLong, │ │2498 }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2499 const link = try std.fmt.allocPrint(aa, "ai:{s}", .{try route.toUrl(session.page_arena)});│ │2500 const label: []const u8 = switch (data.view) { │ │2501 .edit => "edit", │ │2502 + .new_comment => "reply", │ │2503 .resolve => "resolve", │ │2504 else => "new", │ │2505 }; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯