╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2270 try child.widget.input(allocator, key, root_focus); │ │2271 } │ │2272 │ │2269 - fn commentFormInput(self: *This, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │2270 - const form = self.formBox() orelse return; │ │2271 - const cid = form.getFocus().child_id orelse return; │ │2272 - const cur = form.children.getIndex(cid) orelse return; │ │2273 - const child = &form.children.values()[cur]; │ │2274 - │ │2275 - if (cur == comment_body_field_index and try multilineKeepsKey(&child.widget.text_input, allocator, key))│ │2276 - return child.widget.input(allocator, key, root_focus); │ │2277 - │ │2278 - switch (key) { │ │2279 - .arrow_up, .back_tab => if (cur > comment_author_field_index) │ │2280 - root_focus.setFocus(form.children.keys()[cur - 1]) │ │2281 - else │ │2282 - self.focusHeader(root_focus), │ │2283 - .arrow_down, .tab => if (cur < comment_submit_field_index) │ │2284 - root_focus.setFocus(form.children.keys()[cur + 1]), │ │2285 - .enter => if (cur == comment_submit_field_index) try self.submitComment(allocator), │ │2286 - .mouse => |mouse| if (cur == comment_submit_field_index) { │ │2287 - const submit = &child.widget.submit_button; │ │2288 - if (inp.leftClickOn(root_focus, submit.buttonId(), mouse)) try self.submitComment(allocator); │ │2289 - }, │ │2290 - else => try child.widget.input(allocator, key, root_focus), │ │2291 - } │ │2292 - } │ │2293 - │ │2273 fn confirmationFormInput(self: *This, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │2274 const form = self.confirmationForm() orelse return; │ │2275 const child_id = form.getFocus().child_id orelse return; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2309 const on_submit = child.widget == .submit_button; │ │2310 │ │2311 switch (key) { │ │2333 - .arrow_up, .back_tab => if (resolveStep(form, cur, false)) |i| self.focusResolveChild(form, i, root_focus) else self.focusHeader(root_focus),│ │2334 - .arrow_down, .tab => if (resolveStep(form, cur, true)) |i| self.focusResolveChild(form, i, root_focus), │ │2312 + .arrow_up, .back_tab => if (formStep(form, cur, false)) |i| self.focusResolveChild(form, i, root_focus) else self.focusHeader(root_focus), │ │2313 + .arrow_down, .tab => if (formStep(form, cur, true)) |i| self.focusResolveChild(form, i, root_focus), │ │2314 .arrow_left, .arrow_right => switch (child.widget) { │ │2315 .box => |*row| { │ │2316 const row_cid = row.getFocus().child_id orelse return; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │2341 }; │ │2342 } │ │2343 │ │2365 - // the neighboring focusable resolve-form child, skipping the spacer and│ │2366 - // the blank gap rows │ │2367 - fn resolveStep(form: *wgt.Box(Widget), cur: usize, down: bool) ?usize { │ │2344 + // the neighboring form control, skipping spacers and blank rows │ │2345 + fn formStep(form: *wgt.Box(Widget), cur: usize, down: bool) ?usize { │ │2346 var i = cur; │ │2347 while (true) { │ │2348 if (down) { │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │2352 if (i == 0) return null; │ │2353 i -= 1; │ │2354 } │ │2377 - switch (form.children.values()[i].widget) {│ │2355 + const child = &form.children.values()[i]; │ │2356 + if (child.hidden) continue; │ │2357 + switch (child.widget) { │ │2358 .spacer, .text => continue, │ │2359 else => return i, │ │2360 } │ ╰─────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2449 const src = self.data.repo_source orelse return; │ │2450 const author = (try self.session.eventAuthor()) orelse return; │ │2451 const form = self.formBox() orelse return; │ │2472 - const body_input = &form.children.values()[comment_body_field_index].widget.text_input;│ │2452 + const body_input = try formField(form, "body"); │ │2453 const body = try body_input.text(allocator); │ │2454 defer allocator.free(body); │ │2455 if (!evt.Comment.fieldsValid(body)) return; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │2484 try self.session.navigate(route); │ │2485 } │ │2486 │ │2507 - // the terminal submit paths: the new form commits a new thread event, the│ │2508 - // edit form re-emits the selected thread's event with the form's content.│ │2509 - // the web posts the forms to their routes instead. │ │2487 + // terminal form submissions; the web posts to the forms' routes instead │ │2488 fn submitForm(self: *This, allocator: std.mem.Allocator) !void { │ │2511 - if (self.data.view == .edit) { │ │2512 - try self.submitEditedThread(allocator); │ │2513 - } else { │ │2514 - try self.submitNewThread(allocator); │ │2489 + switch (self.data.view) { │ │2490 + .new_comment, .edit_comment => try self.submitComment(allocator), │ │2491 + .edit => try self.submitEditedThread(allocator), │ │2492 + else => try self.submitNewThread(allocator), │ │2493 } │ │2494 } │ │2495 │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2503 const author = (try self.session.eventAuthor()) orelse return; │ │2504 │ │2505 const form = self.threadForm() orelse return; │ │2528 - const target_branch_field_index = form.children.count() - 3; │ │2529 - const title_input = &form.children.values()[title_field_index].widget.text_input; │ │2530 - const tags_input = &form.children.values()[tags_field_index].widget.text_input; │ │2531 - const description_input = &form.children.values()[description_field_index].widget.text_input;│ │2506 + const title_input = try formField(form, "title"); │ │2507 + const tags_input = try formField(form, "tags"); │ │2508 + const description_input = try formField(form, "description"); │ │2509 │ │2510 const title = try title_input.text(allocator); │ │2511 defer allocator.free(title); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2513 defer allocator.free(tags); │ │2514 const description = try description_input.text(allocator); │ │2515 defer allocator.free(description); │ │2539 - const target_branch = if (comptime supports_drafts) try form.children.values()[target_branch_field_index].widget.text_input.text(allocator) else "";│ │2516 + const target_branch = if (comptime supports_drafts) try (try formField(form, "target_branch")).text(allocator) else ""; │ │2517 defer if (comptime supports_drafts) allocator.free(target_branch); │ │2518 │ │2519 if (!Event.fieldsValid(title, tags)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2522 } │ │2523 │ │2524 if (comptime supports_drafts) { │ │2548 - const source = &form.children.values()[source_field_index].widget.patch_source; │ │2525 + const source = formWidget(form, .patch_source) orelse return error.MissingFormField; │ │2526 const source_branch = if (source.existing) try source.field().text(allocator) else null; │ │2527 defer if (source_branch) |branch| allocator.free(branch); │ │2528 const event_id_hex = Data.create(self.data, self.session, allocator, title, tags, description, target_branch, source_branch) catch |err| {│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2533 title_input.clear(allocator); │ │2534 tags_input.clear(allocator); │ │2535 description_input.clear(allocator); │ │2559 - form.children.values()[target_branch_field_index].widget.text_input.clear(allocator); │ │2536 + (try formField(form, "target_branch")).clear(allocator); │ │2537 const route = ui.RoutablePage.repoThreadCommentsRoute(kind, self.data.identity, &event_id_hex, 0) orelse return;│ │2538 try self.session.navigate(route); │ │2539 return; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯