╭ changed ──────────────╮ │src/ui/Repo/Threads.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1678 return; │ │1679 } │ │1680 switch (key) { │ │1743 - .enter, .arrow_right => try self.focusDetail(index, root_focus),│ │1681 + .enter, .arrow_right => self.focusDetail(index, root_focus), │ │1682 else => {}, │ │1683 } │ │1684 } │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1704 else => {}, │ │1705 } │ │1706 if (self.data.comment_page != null and std.mem.eql(u8, self.window(index).items[self.detailed_index[index] orelse return].id, self.data.selected_id)) { │ │1769 - try self.commentInput(allocator, index, key, root_focus); │ │1770 - } else if (self.toolRowFocused(index)) { │ │1707 + self.commentInput(index, key, root_focus); │ │1708 + return; │ │1709 + } │ │1710 + if (self.toolRowFocused(index)) { │ │1711 try self.toolRowInput(allocator, index, key, root_focus); │ │1772 - } else if (self.titleFocused(index)) { │ │1773 - try self.titleInput(index, key, root_focus); │ │1774 - } else if (self.authorFocused(index)) { │ │1775 - try self.authorInput(index, key, root_focus); │ │1776 - } else if (self.tagsFocused(index)) { │ │1777 - try self.tagsInput(index, key, root_focus); │ │1778 - } else if (self.descriptionFocused(index)) { │ │1779 - try self.descriptionInput(index, key, root_focus); │ │1780 - } else if (try self.cloneUrlInput(allocator, index, key, root_focus)) {} else if (try self.attachmentInput(allocator, index, key, root_focus)) {} else {│ │1781 - try self.commentInput(allocator, index, key, root_focus); │ │1712 + return; │ │1713 } │ │1783 - } │ │1714 │ │1785 - fn cloneUrlInput(self: *This, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !bool { │ │1786 - const child_index = self.focusedDetailChild(index, root_focus) orelse return false; │ │1787 - const child = &self.detailInner(index).children.values()[child_index].widget; │ │1788 - const clone_url = switch (child.*) { │ │1789 - .clone_url => |*view| view, │ │1790 - else => return false, │ │1791 - }; │ │1792 - switch (key) { │ │1793 - .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1794 - .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1795 - .arrow_left => if (clone_url.selected == 0) │ │1796 - try self.focusList(index, root_focus) │ │1797 - else │ │1798 - try clone_url.input(allocator, key, root_focus), │ │1799 - else => try clone_url.input(allocator, key, root_focus), │ │1715 + const child_index = self.focusedDetailChild(index, root_focus) orelse return; │ │1716 + const inner = self.detailInner(index); │ │1717 + const child_id = inner.children.keys()[child_index]; │ │1718 + const child = &inner.children.values()[child_index].widget; │ │1719 + │ │1720 + if (child_index == title_child_index) { │ │1721 + switch (key) { │ │1722 + .arrow_left => self.focusList(index, root_focus), │ │1723 + .arrow_up => self.focusToolRow(index, root_focus), │ │1724 + .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1725 + else => {}, │ │1726 + } │ │1727 + return; │ │1728 } │ │1801 - return true; │ │1802 - } │ │1729 + if (child_id == self.author_id[index]) { │ │1730 + // the author's a: link is followed by the host; arrows cross to │ │1731 + // neighboring widgets. │ │1732 + switch (key) { │ │1733 + .arrow_left => self.focusList(index, root_focus), │ │1734 + .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1735 + .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1736 + else => {}, │ │1737 + } │ │1738 + return; │ │1739 + } │ │1740 + if (child_id == self.description_id[index]) { │ │1741 + self.descriptionInput(index, key, root_focus); │ │1742 + return; │ │1743 + } │ │1744 │ │1804 - fn attachmentInput(self: *This, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !bool { │ │1805 - const child_index = self.focusedDetailChild(index, root_focus) orelse return false; │ │1806 - const child = &self.detailInner(index).children.values()[child_index].widget; │ │1807 - const row = switch (child.*) { │ │1808 - .box => |*box| box, │ │1809 - else => return false, │ │1810 - }; │ │1811 - const attachment_id = Attachment.removeId(row) orelse return false; │ │1812 - if (row.children.count() < 2) return false; │ │1813 - const remove_id = row.children.keys()[1]; │ │1814 - switch (key) { │ │1815 - .arrow_left => if (!self.moveCommentHorizontal(index, root_focus, false)) try self.focusList(index, root_focus), │ │1816 - .arrow_right => _ = self.moveCommentHorizontal(index, root_focus, true), │ │1817 - .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1818 - .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1819 - .enter => if (row.getFocus().child_id == remove_id) try self.removeEvent(allocator, .attach, attachment_id), │ │1820 - .mouse => |mouse| if (inp.leftClickOn(root_focus, remove_id, mouse)) try self.removeEvent(allocator, .attach, attachment_id), │ │1745 + switch (child.*) { │ │1746 + .tag_flow => |*tf| { │ │1747 + self.tagsInput(index, child_index, tf, key, root_focus); │ │1748 + return; │ │1749 + }, │ │1750 + .clone_url => |*clone_url| { │ │1751 + switch (key) { │ │1752 + .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1753 + .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1754 + .arrow_left => if (clone_url.selected == 0) │ │1755 + self.focusList(index, root_focus) │ │1756 + else │ │1757 + try clone_url.input(allocator, key, root_focus), │ │1758 + else => try clone_url.input(allocator, key, root_focus), │ │1759 + } │ │1760 + return; │ │1761 + }, │ │1762 + .box => |*row| if (Attachment.removeId(row)) |attachment_id| { │ │1763 + const remove_id = row.children.keys()[1]; │ │1764 + switch (key) { │ │1765 + .arrow_left => if (!self.moveCommentHorizontal(index, root_focus, false)) self.focusList(index, root_focus), │ │1766 + .arrow_right => _ = self.moveCommentHorizontal(index, root_focus, true), │ │1767 + .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1768 + .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1769 + .enter => if (row.getFocus().child_id == remove_id) try self.removeEvent(allocator, .attach, attachment_id), │ │1770 + .mouse => |mouse| if (inp.leftClickOn(root_focus, remove_id, mouse)) try self.removeEvent(allocator, .attach, attachment_id), │ │1771 + else => {}, │ │1772 + } │ │1773 + return; │ │1774 + }, │ │1775 else => {}, │ │1776 } │ │1823 - return true; │ │1777 + self.commentInput(index, key, root_focus); │ │1778 } │ │1779 │ │1780 // enter or a click runs the primary action; links are handled by the host │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1781 fn toolRowInput(self: *This, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !void { │ │1782 const row = self.toolRow(index); │ │1783 const cur = if (row.getFocus().child_id) |cid| row.children.getIndex(cid) orelse return else return; │ │1830 - const on_primary = if (self.primary_button_id[index]) |id| row.getFocus().child_id == id else false; │ │1784 + const child_id = row.children.keys()[cur]; │ │1785 + const on_primary = switch (row.children.values()[cur].widget.getFocus().kind) { │ │1786 + .custom => |action| std.mem.eql(u8, action, "submit"), │ │1787 + else => false, │ │1788 + }; │ │1789 switch (key) { │ │1832 - .arrow_left => if (cur > first_in_row_index) root_focus.setFocus(row.children.keys()[cur - 1]) else try self.focusList(index, root_focus),│ │1790 + .arrow_left => if (cur > first_in_row_index) root_focus.setFocus(row.children.keys()[cur - 1]) else self.focusList(index, root_focus), │ │1791 .arrow_right => if (cur + 1 < row.children.count()) root_focus.setFocus(row.children.keys()[cur + 1]), │ │1792 .arrow_up => self.focusHeader(root_focus), │ │1793 .arrow_down => { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1795 self.detailScroll(index).y = 0; │ │1796 }, │ │1797 .enter => if (on_primary) try self.primaryAction(allocator, index), │ │1840 - .mouse => |mouse| if (self.primary_button_id[index]) |id| { │ │1841 - if (inp.leftClickOn(root_focus, id, mouse)) try self.primaryAction(allocator, index); │ │1842 - }, │ │1798 + .mouse => |mouse| if (on_primary and inp.leftClickOn(root_focus, child_id, mouse)) try self.primaryAction(allocator, index),│ │1799 else => {}, │ │1800 } │ │1801 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1817 try self.data.postDraft(self.session, allocator, entry.id); │ │1818 const route = listRoute(self.data.identity, @enumFromInt(0), "", entry.id) orelse return;│ │1819 try self.session.navigate(route); │ │1864 - } │ │1865 - │ │1866 - fn titleInput(self: *This, index: usize, key: Key, root_focus: *Focus) !void { │ │1867 - switch (key) { │ │1868 - .arrow_left => try self.focusList(index, root_focus), │ │1869 - .arrow_up => self.focusToolRow(index, root_focus), │ │1870 - .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1871 - else => {}, │ │1872 - } │ │1873 - } │ │1874 - │ │1875 - // the author box's a: link (when it has one) is followed by the host on │ │1876 - // enter / a click; arrows cross to the neighboring widgets. │ │1877 - fn authorInput(self: *This, index: usize, key: Key, root_focus: *Focus) !void { │ │1878 - switch (key) { │ │1879 - .arrow_left => try self.focusList(index, root_focus), │ │1880 - .arrow_up => _ = self.moveDetailVertical(index, root_focus, false), │ │1881 - .arrow_down => _ = self.moveDetailVertical(index, root_focus, true), │ │1882 - else => {}, │ │1883 - } │ │1820 } │ │1821 │ │1886 - fn descriptionInput(self: *This, index: usize, key: Key, root_focus: *Focus) !void { │ │1822 + fn descriptionInput(self: *This, index: usize, key: Key, root_focus: *Focus) void { │ │1823 const sc = self.detailScroll(index); │ │1824 switch (key) { │ │1825 .arrow_left => return self.focusList(index, root_focus), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1851 }, │ │1852 else => return, │ │1853 } │ │1918 - sc.clampToContent(); │ │1854 } │ │1855 │ │1921 - fn commentInput(self: *This, allocator: std.mem.Allocator, index: usize, key: Key, root_focus: *Focus) !void { │ │1922 - _ = allocator; │ │1856 + fn commentInput(self: *This, index: usize, key: Key, root_focus: *Focus) void { │ │1857 switch (key) { │ │1858 .arrow_left => if (!self.moveCommentHorizontal(index, root_focus, false)) return self.focusList(index, root_focus),│ │1859 .arrow_right => _ = self.moveCommentHorizontal(index, root_focus, true), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │2219 } │ │2220 } │ │2221 │ │2288 - // re-emit the conflicted issue's event with the resolved content; any │ │2289 - // event on the issue settles its conflict entry. this is the terminal │ │2222 + // re-emit the conflicted thread's event with the resolved content; any│ │2223 + // event on the thread settles its conflict entry. this is the terminal│ │2224 // path; the web posts the form to the resolve route. │ │2225 fn submitResolution(self: *This, allocator: std.mem.Allocator) !void { │ │2226 if (comptime wasm) return; │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2329 } │ │2330 │ │2331 body_input.clear(allocator); │ │2398 - const route = commentRoute(self.data.identity, &thread_id, &event_id_hex, 0) orelse return; │ │2332 + const route = ui.RoutablePage.repoThreadCommentRoute(kind, self.data.identity, &thread_id, &event_id_hex, 0) orelse return;│ │2333 try self.session.navigate(route); │ │2334 } │ │2335 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │2402 - // the terminal submit paths: the new form commits a new-issue event, the │ │2403 - // edit form re-emits the selected issue's event with the form's content. │ │2336 + // the terminal submit paths: the new form commits a new thread event, the│ │2337 + // edit form re-emits the selected thread's event with the form's content.│ │2338 // the web posts the forms to their routes instead. │ │2339 fn submitForm(self: *This, allocator: std.mem.Allocator) !void { │ │2340 if (self.data.view == .edit) { │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────╮ │2344 } │ │2345 } │ │2346 │ │2413 - // commit the new issue to the repo's events branch and navigate to it. │ │2414 - // this is the terminal path; the web posts the form to the issue route, │ │2347 + // commit the new thread to the repo's events branch and navigate to it. │ │2348 + // this is the terminal path; the web posts the form to the thread route,│ │2349 // so the wasm side never runs (or compiles) the repo access below. │ │2350 fn submitNewThread(self: *This, allocator: std.mem.Allocator) !void { │ │2351 if (comptime wasm) return; │ ╰───────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯