╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ───────────────╮ │src/ui/widget/thread.zig│ ╰────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │865 "(no description)" │ │866 else │ │867 whole; │ │867 - var description = try wgt.TextBox.init(allocator, shown, .{ │ │868 - .border_style = .single, │ │869 - .round_corners = true, │ │870 - .wrap_kind = .word, │ │871 - .label = " description ", │ │872 - .bottom_label = if (preview_end != null) " click or press enter to see more " else "", │ │873 - }); │ │868 + var description = try Markdown.Frame.init(allocator, shown, " description ", if (preview_end != null) " click or press enter to see more " else "", self.session.page_arena);│ │869 errdefer description.deinit(allocator); │ │875 - description.getFocus().mode = .all; │ │870 if (preview_end != null) { │ │871 const route = ui.RoutablePage.repoThreadDescriptionRoute(kind, self.data.identity, entry.id) orelse return error.RouteTooLong; │ │878 - description.getFocus().kind = .{ .custom = try std.fmt.allocPrint(self.session.page_arena.allocator(), "a:{s}", .{try route.toUrl(self.session.page_arena)}) }; │ │872 + description.body().kind = .{ .custom = try std.fmt.allocPrint(self.session.page_arena.allocator(), "a:{s}", .{try route.toUrl(self.session.page_arena)}) }; │ │873 } │ │880 - try inner_box.children.put(allocator, description.getFocus().id, .{ .widget = .{ .text_box = description }, .rect = null, .min_size = null }); │ │874 + try inner_box.children.put(allocator, description.getFocus().id, .{ .widget = .{ .markdown_frame = description }, .rect = null, .min_size = null }); │ │875 self.description_id = description.getFocus().id; │ │876 │ │877 if (!supports_drafts or !entryDraft(entry)) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │920 .page_down => self.pageDetail(root_focus, 10), │ │921 .home => self.jump(root_focus, false), │ │922 .end => self.jump(root_focus, true), │ │923 + // tab steps between the links of a focused description or │ │924 + // comment; the web leaves it to the browser │ │925 + .tab, .back_tab => if (self.session.is_terminal) if (self.focusedChild(root_focus)) |index| if (self.focusedFrame(index)) |frame| {│ │926 + try frame.frame.stepLink(allocator, root_focus, &self.scroll, frame.x, frame.y, key == .tab); │ │927 + }, │ │928 else => { │ │929 const entry = self.entry orelse return self.exit; │ │930 if (self.data.comment_page != null and std.mem.eql(u8, entry.id, self.data.selected_id)) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1135 return false; │ │1136 } │ │1137 │ │1138 + // the markdown frame focused in child `child_index`, placed in the scroll's │ │1139 + // content │ │1140 + fn focusedFrame(self: *This, child_index: usize) ?struct { frame: *Markdown.Frame, x: isize, y: isize } {│ │1141 + const child = &self.inner().children.values()[child_index]; │ │1142 + const rect = child.rect orelse return null; │ │1143 + switch (child.widget) { │ │1144 + .markdown_frame => |*frame| return .{ .frame = frame, .x = rect.x, .y = rect.y }, │ │1145 + .repo_comment => |*comment| { │ │1146 + if (!comment.bodyFocused()) return null; │ │1147 + const row = comment.rowRect(true) orelse return null; │ │1148 + return .{ .frame = comment.body(), .x = rect.x + row.x, .y = rect.y + row.y }; │ │1149 + }, │ │1150 + else => return null, │ │1151 + } │ │1152 + } │ │1153 + │ │1154 fn focusedChild(self: *This, root_focus: *Focus) ?usize { │ │1155 const inner_box = self.inner(); │ │1156 var id = root_focus.grandchild_id orelse return null; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1823 } │ │1824 │ │1825 { │ │1811 - var description = try wgt.TextInput.init(allocator, .{ .label = " description ", .name = "description", .visible_width = null, .round_corners = true, .render_content = session.is_terminal, .multiline = true, .scroll = .{ .fill = true } }); │ │1826 + var description = try wgt.TextInput.init(allocator, .{ .label = " description (markdown supported) ", .name = "description", .visible_width = null, .round_corners = true, .render_content = session.is_terminal, .multiline = true, .scroll = .{ .fill = true } });│ │1827 errdefer description.deinit(allocator); │ │1828 description.getFocus().mode = .all; │ │1829 if (saved_fields) |saved| │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────╮ │1869 │ │1870 { │ │1871 var body = try wgt.TextInput.init(allocator, .{│ │1857 - .label = " comment ", │ │1872 + .label = " comment (markdown supported) ", │ │1873 .name = "body", │ │1874 .visible_width = null, │ │1875 .round_corners = true, │ ╰─────────────────────────────────────────────────────────────────────╯