Skip to content

Commit

Permalink
feat(autocomplete): link to text fragment
Browse files Browse the repository at this point in the history
  • Loading branch information
thecristen committed Nov 1, 2023
1 parent ee26428 commit c9b04ce
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 5 deletions.
43 changes: 43 additions & 0 deletions apps/site/assets/ts/ui/__tests__/autocomplete-templates-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { LinkForItem } from "../autocomplete/templates/algolia";
import { AutocompleteItem } from "../autocomplete/__autocomplete";

test("LinkForItem renders simplelink", () => {
const contentItem = {
_content_url: "/page1",
_content_type: "page",
content_title: "Page One",
index: "drupal",
objectID: "sdfsda",
_highlightResult: { content_title: { matchedWords: ["one"] } }
} as AutocompleteItem;
render(
<LinkForItem item={contentItem} query="one">
<div>Content</div>
</LinkForItem>
);

expect(screen.getByRole("link")).toHaveAttribute("href", "/page1");
});

test("LinkForItem renders link to text fragment", () => {
const contentItemNoMatch = {
_content_url: "/page2",
_content_type: "page",
content_title: "Page Two",
index: "drupal",
objectID: "sdfsda",
_highlightResult: { content_title: { matchedWords: [] } }
} as AutocompleteItem;
render(
<LinkForItem item={contentItemNoMatch} query="searched text">
<div>Content</div>
</LinkForItem>
);

expect(screen.getByRole("link")).toHaveAttribute(
"href",
"/page2#:~:text=searched%20text"
);
});
45 changes: 40 additions & 5 deletions apps/site/assets/ts/ui/autocomplete/templates/algolia.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-underscore-dangle */
import React from "react";
import { uniqueId } from "lodash";
import { get, uniqueId } from "lodash";
import { SourceTemplates } from "@algolia/autocomplete-js";
import { AutocompleteItem, Item } from "../__autocomplete";
import { getTitleAttribute, isContentItem, isRouteItem } from "../helpers";
Expand All @@ -9,20 +10,54 @@ import {
getIcon
} from "../../../../js/algolia-result";

interface LinkForItemProps {
item: AutocompleteItem;
query: string;
children: React.ReactElement;
}
export function LinkForItem(props: LinkForItemProps): React.ReactElement {
const { item, query, children } = props;
const url = isContentItem(item) ? item._content_url : item.url;

// Special case: When the matching text isn't part of the page title, help the
// user locate the matching text by linking directly to / scrolling to the
// matching text on the page.
const highlightedResult = get(item._highlightResult, getTitleAttribute(item));
if (
isContentItem(item) &&
highlightedResult &&
highlightedResult.matchedWords.length === 0
) {
// link directly to queried text via URL fragment text directive, supported
// in most browsers, ignored by the others. works with Turbolinks disabled.
const urlToQuery = `${url}#:~:text=${encodeURIComponent(query)}`;
return (
<a href={urlToQuery} className="aa-ItemLink" data-turbolinks="false">
{children}
</a>
);
}

return (
<a href={url} className="aa-ItemLink">
{children}
</a>
);
}

const AlgoliaItemTemplate: SourceTemplates<Item>["item"] = ({
item,
state,
components
}) => {
const { index } = item as AutocompleteItem;
const attribute = getTitleAttribute(item);
const featureIcons = getFeatureIcons(item, index);
// eslint-disable-next-line no-underscore-dangle
const url = isContentItem(item) ? item._content_url : item.url;
const iconHtml = isContentItem(item)
? contentIcon(item)
: getIcon(item, index);
return (
<a href={url} className="aa-ItemLink">
<LinkForItem item={item as AutocompleteItem} query={state.query}>
<div className="aa-ItemContent">
<span
// eslint-disable-next-line react/no-danger
Expand Down Expand Up @@ -57,7 +92,7 @@ const AlgoliaItemTemplate: SourceTemplates<Item>["item"] = ({
</span>
</span>
</div>
</a>
</LinkForItem>
);
};

Expand Down

0 comments on commit c9b04ce

Please sign in to comment.