╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │1194 } │ │1195 │ │1196 // true when `a` and `b` are the same user paginated to a different repos │ │1197 - // window. switching between a user's tabs is in-page (header-handled), so│ │1197 + // window. switching between a user's tabs is in-page, so │ │1198 // only a changed `start` on the repos list navigates. │ │1199 pub fn userPageChanged(a: RoutablePage, b: RoutablePage) bool { │ │1200 return switch (a) { │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1654 } │ │1655 │ │1656 pub fn inputKey(allocator: std.mem.Allocator, root: *Widget, key: Key, session: *Session) !void {│ │1657 + const root_focus = root.getFocus(); │ │1658 + const focused_before = root_focus.grandchild_id; │ │1659 + var activate_in_page_link = false; │ │1660 switch (key) { │ │1661 // request a navigation pop; the host's Nav.sync goes back a page, or │ │1662 // quits when there's no history left. │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1660 - .escape => session.nav_back = true, │ │1663 + .escape => { │ │1664 + session.nav_back = true; │ │1665 + return; │ │1666 + }, │ │1667 .ctrl => |letter| switch (letter) { │ │1662 - 'r' => session.refresh_requested = true, │ │1663 - else => try root.input(allocator, key, root.getFocus()), │ │1668 + 'r' => { │ │1669 + session.refresh_requested = true; │ │1670 + return; │ │1671 + }, │ │1672 + else => try root.input(allocator, key, root_focus), │ │1673 }, │ │1674 .enter => { │ │1666 - const root_focus = root.getFocus(); │ │1675 if (root_focus.grandchild_id) |gid| { │ │1676 // follow a cross-page link │ │1677 if (crossPageLink(root_focus, gid, session.data)) |route| {│ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │1679 } │ │1680 if (rawLink(root_focus, gid)) |url| return requestRawLink(session, url);│ │1681 } │ │1682 + activate_in_page_link = true; │ │1683 try root.input(allocator, key, root_focus); │ │1684 }, │ │1685 .mouse => |mouse| { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────╮ │1686 if (mouse.action == .press and mouse.action.press == .left) {│ │1678 - const root_focus = root.getFocus(); │ │1687 var clicked: ?usize = null; │ │1688 var iter = root_focus.children.iterator(); │ │1689 while (iter.next()) |entry| { │ ╰───────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │1707 return; │ │1708 } │ │1709 root_focus.setFocus(focus_id); │ │1710 + activate_in_page_link = true; │ │1711 } │ │1712 // forward the press into the widget tree so buttons (and any│ │1713 // future click-aware widgets) can react. widgets that don't │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1714 // care about presses ignore it. │ │1706 - try root.input(allocator, key, root.getFocus()); │ │1715 + try root.input(allocator, key, root_focus); │ │1716 } else { │ │1708 - try root.input(allocator, key, root.getFocus()); │ │1717 + try root.input(allocator, key, root_focus); │ │1718 } │ │1719 }, │ │1711 - else => try root.input(allocator, key, root.getFocus()), │ │1720 + else => try root.input(allocator, key, root_focus), │ │1721 } │ │1722 + │ │1723 + const focused_after = root_focus.grandchild_id; │ │1724 + if (!activate_in_page_link and focused_before == focused_after) return; │ │1725 + const focus_id = focused_after orelse return; │ │1726 + if (inPageLink(root_focus, focus_id, session.data)) |route| session.data.current_page = route;│ │1727 } │ │1728 │ │1729 // a link to bytes the server serves directly rather than to a page route, so │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1730 // it never resolves to a RoutablePage │ │1731 pub const raw_link_prefix = "ax:"; │ │1732 + │ │1733 +// a route already loaded into the current page; focusing it changes the url │ │1734 +// without rebuilding the page │ │1735 +pub const in_page_link_prefix = "ai:"; │ │1736 │ │1737 +// an in-page tab's base route, or the exact current route when it is selected │ │1738 +pub fn inPageTabLink(session: *Session, route: RoutablePage, selected: bool) ![]const u8 { │ │1739 + const target = if (selected) session.data.current_page else route; │ │1740 + return std.fmt.allocPrint(session.page_arena.allocator(), "{s}{s}", .{ in_page_link_prefix, try target.toUrl(session.page_arena) });│ │1741 +} │ │1742 + │ │1743 // a button the web overlay covers with a file picker that posts the chosen │ │1744 // file to the url after the prefix │ │1745 pub const file_input_prefix = "file:"; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1776 // link into a navigation rather than a focus change. │ │1777 pub fn crossPageLink(root_focus: *Focus, focus_id: usize, data: Session.Data) ?RoutablePage { │ │1778 const current = data.current_page; │ │1779 + const route = pageLink(root_focus, focus_id, data, "a:") orelse return null; │ │1780 + // a link to a different parent page always navigates; within a page, a │ │1781 + // files-directory / commits-page / list-window change navigates while tab │ │1782 + // links stay in-page. │ │1783 + if (route.parent() != current.parent() or RoutablePage.repoPageChanged(route, current) or RoutablePage.homePageChanged(route, current) or RoutablePage.userPageChanged(route, current)) return route;│ │1784 + return null; │ │1785 +} │ │1786 + │ │1787 +// the route carried by an `ai:` focus target, or null for any other target │ │1788 +pub fn inPageLink(root_focus: *Focus, focus_id: usize, data: Session.Data) ?RoutablePage { │ │1789 + return pageLink(root_focus, focus_id, data, in_page_link_prefix); │ │1790 +} │ │1791 + │ │1792 +fn pageLink(root_focus: *Focus, focus_id: usize, data: Session.Data, prefix: []const u8) ?RoutablePage { │ │1793 const child = root_focus.children.get(focus_id) orelse return null; │ │1794 const custom = switch (child.focus.kind) { │ │1795 .custom => |c| c, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1796 else => return null, │ │1797 }; │ │1760 - const a_prefix = "a:"; │ │1761 - if (!std.mem.startsWith(u8, custom, a_prefix)) return null; │ │1762 - const path = custom[a_prefix.len..]; │ │1798 + if (!std.mem.startsWith(u8, custom, prefix)) return null; │ │1799 + const path = custom[prefix.len..]; │ │1800 // local sessions build (and parse) links with the repo identity elided │ │1764 - const route = (if (data.is_local) RoutablePage.fromUrlLocal(path) else RoutablePage.fromUrl(path)) orelse return null; │ │1765 - // a link to a different parent page always navigates; within a page, a │ │1766 - // files-directory / commits-page / list-window change navigates while tab │ │1767 - // links stay in-page (header-handled). │ │1768 - if (route.parent() != current.parent() or RoutablePage.repoPageChanged(route, current) or RoutablePage.homePageChanged(route, current) or RoutablePage.userPageChanged(route, current)) return route;│ │1769 - return null; │ │1801 + return if (data.is_local) RoutablePage.fromUrlLocal(path) else RoutablePage.fromUrl(path); │ │1802 } │ │1803 │ │1804 // a display author, resolved against the admin db at read time │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯