╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Threads.zig│ ╰───────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1093 try row.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = .{ .width = try xitui.width.displayWidth(label) + 2, .height = null } }); │ │1094 } │ │1095 │ │1096 + fn addDetailGap(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget)) !void { │ │1097 + var spacer = try ui.Spacer.init(allocator); │ │1098 + errdefer spacer.deinit(allocator); │ │1099 + try box.children.put(allocator, spacer.getFocus().id, .{ .widget = .{ .spacer = spacer }, .rect = null, .min_size = .{ .width = null, .height = 1 }, .max_size = .{ .width = null, .height = 1 } });│ │1100 + } │ │1101 + │ │1102 pub fn deinit(self: *This, allocator: std.mem.Allocator) void { │ │1103 self.box.deinit(allocator); │ │1104 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │1338 for (inner.children.values()) |*child| child.widget.deinit(allocator);│ │1339 inner.children.clearAndFree(allocator); │ │1340 inner.getFocus().child_id = null; │ │1341 + self.title_id[index] = null; │ │1342 self.author_id[index] = null; │ │1343 │ │1344 // the tool row; the description page shows none. │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1417 } │ │1418 } │ │1419 │ │1420 + if (has_drafts and entryDraft(entry) and !description_page and comment_page == null) { │ │1421 + const aa = self.session.page_arena.allocator(); │ │1422 + var fields: [2]ui.CopyableText.Choice = undefined; │ │1423 + var field_count: usize = 0; │ │1424 + │ │1425 + if (entry.target_branch.len != 0) if (self.session.data.git_ssh_port) |port| { │ │1426 + const url = try std.fmt.allocPrint(aa, "ssh://localhost:{d}/repo/{s}/patch:{s}/branch:{s}", .{ port, self.data.identity, entry.id, entry.target_branch });│ │1427 + const push_command = try std.fmt.allocPrint(aa, "git push {s} HEAD:patch", .{url}); │ │1428 + const clone_name = try Data.cloneDirectoryName(aa, entry.record.event.title); │ │1429 + const clone_command = if (clone_name.len == 0) │ │1430 + try std.fmt.allocPrint(aa, "git clone {s}", .{url}) │ │1431 + else │ │1432 + try std.fmt.allocPrint(aa, "git clone {s} {s}", .{ url, clone_name }); │ │1433 + const choices: [2]ui.CopyableText.Choice = .{ │ │1434 + .{ │ │1435 + .selector = "push", │ │1436 + .text = push_command, │ │1437 + .copyable_text = try std.fmt.allocPrint(aa, "{s}{s}", .{ self.session.data.git_ssh_prefix, push_command }), │ │1438 + .label = " push to this patch from existing repo ", │ │1439 + }, │ │1440 + .{ │ │1441 + .selector = "clone", │ │1442 + .text = clone_command, │ │1443 + .copyable_text = try std.fmt.allocPrint(aa, "{s}{s}", .{ self.session.data.git_ssh_prefix, clone_command }), │ │1444 + .label = " clone this patch ", │ │1445 + }, │ │1446 + }; │ │1447 + var copyable_text = try ui.CopyableText.View.init(allocator, self.session, &choices); │ │1448 + errdefer copyable_text.deinit(allocator); │ │1449 + try inner.children.put(allocator, copyable_text.getFocus().id, .{ .widget = .{ .copyable_text = copyable_text }, .rect = null, .min_size = null }); │ │1450 + try addDetailGap(allocator, inner); │ │1451 + }; │ │1452 + if (entry.fork_oid.len != 0) { │ │1453 + fields[field_count] = .{ │ │1454 + .text = entry.fork_oid, │ │1455 + .label = if (entry.no_changes) " object id of this patch (no changes) " else " object id of this patch ", │ │1456 + }; │ │1457 + field_count += 1; │ │1458 + } │ │1459 + if (entry.target_branch.len != 0) { │ │1460 + fields[field_count] = .{ │ │1461 + .text = entry.target_branch, │ │1462 + .label = " target branch this patch will go to ", │ │1463 + .bottom_label = " (set by the url you push to above) ", │ │1464 + }; │ │1465 + field_count += 1; │ │1466 + } │ │1467 + │ │1468 + for (fields[0..field_count], 0..) |field, i| { │ │1469 + var copyable_text = try ui.CopyableText.View.init(allocator, self.session, &.{field}); │ │1470 + errdefer copyable_text.deinit(allocator); │ │1471 + try inner.children.put(allocator, copyable_text.getFocus().id, .{ .widget = .{ .copyable_text = copyable_text }, .rect = null, .min_size = null }); │ │1472 + if (i + 1 < field_count) try addDetailGap(allocator, inner); │ │1473 + } │ │1474 + if (entry.target_branch.len != 0) try addDetailGap(allocator, inner); │ │1475 + } │ │1476 + │ │1477 if (comment_page) |page| { │ │1478 var back_label_buf: ["← back to ".len + thread_name.len]u8 = undefined; │ │1479 const back_label = try std.fmt.bufPrint(&back_label_buf, "← back to {s}", .{thread_name}); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1508 tb.getFocus().focusable = true; │ │1509 tb.getFocus().kind = .{ .custom = try listLink(self.session.page_arena, self.data.identity, entryStatus(entry), "", entry.id) }; │ │1510 try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ │1511 + self.title_id[index] = tb.getFocus().id; │ │1512 } else { │ │1513 // the thread's title as a focusable word-wrapped text box. │ │1447 - { │ │1514 + self.title_id[index] = blk: { │ │1515 var tb = try wgt.TextBox(ui.Widget).init(allocator, entry.record.event.title, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .word, .label = " title " });│ │1516 errdefer tb.deinit(allocator); │ │1517 tb.getFocus().focusable = true; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1518 try inner.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null });│ │1452 - } │ │1519 + break :blk tb.getFocus().id; │ │1520 + }; │ │1521 │ │1522 self.author_id[index] = blk: { │ │1523 var tb = try ui.authorBox(allocator, self.session.page_arena, entry.author); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1575 }; │ │1576 │ │1577 if (has_drafts and entryDraft(entry)) { │ │1510 - if (self.data.target_branch.len != 0 and self.session.data.git_ssh_port != null) { │ │1511 - var push = try ui.GitRemote.View.initPush(allocator, self.session, self.data.identity, entry.id, self.data.target_branch); │ │1512 - errdefer push.deinit(allocator); │ │1513 - try inner.children.put(allocator, push.getFocus().id, .{ .widget = .{ .git_remote = push }, .rect = null, .min_size = null });│ │1514 - } │ │1515 - inner.getFocus().child_id = inner.children.keys()[title_child_index]; │ │1578 + inner.getFocus().child_id = inner.children.keys()[0]; │ │1579 const sc = self.detailScroll(index); │ │1580 sc.x = 0; │ │1581 sc.y = 0; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │1601 } │ │1602 │ │1603 // select the title by default │ │1541 - inner.getFocus().child_id = inner.children.keys()[title_child_index]; │ │1604 + if (self.title_id[index]) |id| inner.getFocus().child_id = id; │ │1605 │ │1606 // reset the scroll to the top for the newly-shown thread: directly on the│ │1607 // terminal (the wasm offset), and via a version bump on the web (so the │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1728 const child_id = inner.children.keys()[child_index]; │ │1729 const child = &inner.children.values()[child_index].widget; │ │1730 │ │1668 - if (child_index == title_child_index) { │ │1731 + if (child_id == self.title_id[index]) { │ │1732 switch (key) { │ │1733 .arrow_left => self.focusList(index, root_focus), │ │1671 - .arrow_up => self.focusToolRow(index, root_focus), │ │1734 + .arrow_up => if (!self.moveDetailVertical(index, root_focus, false)) self.focusToolRow(index, root_focus),│ │1735 .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1736 else => {}, │ │1737 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1758 self.tagsInput(index, child_index, tf, key, root_focus); │ │1759 return; │ │1760 }, │ │1698 - .git_remote => |*git_remote| { │ │1761 + .copyable_text => |*copyable_text| { │ │1762 switch (key) { │ │1700 - .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1763 + .arrow_up => if (!self.moveDetailVertical(index, root_focus, false)) self.focusToolRow(index, root_focus),│ │1764 .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1702 - .arrow_left => if (git_remote.selected == 0) │ │1765 + .arrow_left => if (copyable_text.selected == 0) │ │1766 self.focusList(index, root_focus) │ │1767 else │ │1705 - try git_remote.input(allocator, key, root_focus), │ │1706 - else => try git_remote.input(allocator, key, root_focus), │ │1768 + try copyable_text.input(allocator, key, root_focus), │ │1769 + else => try copyable_text.input(allocator, key, root_focus), │ │1770 } │ │1771 return; │ │1772 }, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │2552 } │ │2553 } │ │2554 │ │2492 - const title_child_index: usize = 0; │ │2493 - │ │2555 // the tool row's first child after the spacer that pushes it right│ │2556 const first_in_row_index: usize = 1; │ │2557 │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯