Fix QL Copilot thing

This commit is contained in:
shamoon
2026-08-21 14:00:24 -07:00
parent 4c361422ca
commit 7d48b7ea78
2 changed files with 61 additions and 19 deletions
+13 -12
View File
@@ -15,6 +15,16 @@ const MOBILE_BUTTON_POSITIONS = {
"bottom-right": "bottom-4 right-4", "bottom-right": "bottom-4 right-4",
}; };
function parseUrl(searchString) {
if (!/.+[.:].+/.test(searchString)) return null; // basic test for probably a url
try {
return new URL(searchString.toLowerCase().startsWith("http") ? searchString : `https://${searchString}`);
} catch {
return null;
}
}
function getSearchResults({ function getSearchResults({
hideVisitURL, hideVisitURL,
searchDescriptions, searchDescriptions,
@@ -77,7 +87,7 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
const searchField = useRef(); const searchField = useRef();
const [currentItemIndex, setCurrentItemIndex] = useState(null); const [currentItemIndex, setCurrentItemIndex] = useState(null);
const [url, setUrl] = useState(null); const url = parseUrl(searchString);
const [searchSuggestions, setSearchSuggestions] = useState([]); const [searchSuggestions, setSearchSuggestions] = useState([]);
const { data: widgets } = useSWR("/api/widgets"); const { data: widgets } = useSWR("/api/widgets");
@@ -134,17 +144,8 @@ export default function QuickLaunch({ servicesAndBookmarks, searchString, setSea
function handleSearchChange(event) { function handleSearchChange(event) {
const rawSearchString = event.target.value; const rawSearchString = event.target.value;
setCurrentItemIndex(null); setCurrentItemIndex(null);
try { // urls keep their casing, everything else is lowercased for matching
if (!/.+[.:].+/g.test(rawSearchString)) throw new Error(); // basic test for probably a url setSearchString(parseUrl(rawSearchString) ? rawSearchString : rawSearchString.toLowerCase());
let urlString = rawSearchString;
if (urlString.toLowerCase().indexOf("http") !== 0) urlString = `https://${rawSearchString}`;
setUrl(new URL(urlString)); // basic validation
setSearchString(rawSearchString);
return;
} catch (e) {
setUrl(null);
}
setSearchString(rawSearchString.toLowerCase());
} }
function handleSearchKeyDown(event) { function handleSearchKeyDown(event) {
+48 -7
View File
@@ -46,13 +46,25 @@ function Wrapper({ servicesAndBookmarks = [], initialOpen = true } = {}) {
const [isOpen, setSearching] = useState(initialOpen); const [isOpen, setSearching] = useState(initialOpen);
return ( return (
<QuickLaunch <>
servicesAndBookmarks={servicesAndBookmarks} <button
searchString={searchString} type="button"
setSearchString={setSearchString} data-testid="seed-key"
isOpen={isOpen} onClick={() => {
setSearching={setSearching} setSearchString((current) => `${current}g`);
/> setSearching(true);
}}
>
seed
</button>
<QuickLaunch
servicesAndBookmarks={servicesAndBookmarks}
searchString={searchString}
setSearchString={setSearchString}
isOpen={isOpen}
setSearching={setSearching}
/>
</>
); );
} }
@@ -151,6 +163,35 @@ describe("components/quicklaunch", () => {
openSpy.mockRestore(); openSpy.mockRestore();
}); });
it("does not carry a previous url result into a search seeded by a keypress", async () => {
renderWithProviders(<Wrapper />, {
settings: {
target: "_self",
quicklaunch: {
provider: "duckduckgo",
showSearchSuggestions: false,
},
},
});
const input = screen.getByPlaceholderText("Search");
await waitFor(() => expect(input).toHaveFocus());
fireEvent.change(input, { target: { value: "example.com" } });
expect(await screen.findByText("quicklaunch.visit URL")).toBeInTheDocument();
fireEvent.keyDown(input, { key: "Escape" });
await act(async () => {
await new Promise((r) => setTimeout(r, 350));
});
// reopen by "typing" a character, the way pages/index does
fireEvent.click(screen.getByTestId("seed-key"));
expect(input).toHaveValue("g");
expect(screen.queryByText("quicklaunch.visit URL")).not.toBeInTheDocument();
});
it("closes on Escape and clears the search string after the timeout", async () => { it("closes on Escape and clears the search string after the timeout", async () => {
renderWithProviders(<Wrapper />, { renderWithProviders(<Wrapper />, {
settings: { settings: {