Skip to content

Commit 31cc450

Browse files
committed
Restore search of the API docs reworked in a new component consistent with the component from the guides in style
- Now navigable by screen readers (tested with Voiceover) - Not 100% spec a11y but a big improvement over the previous search that was not usable at all by screen readers
1 parent 139e93c commit 31cc450

File tree

18 files changed

+848
-537
lines changed

18 files changed

+848
-537
lines changed

app/components/api-search.gjs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import Component from '@glimmer/component';
2+
import { service } from '@ember/service';
3+
import { LinkTo } from '@ember/routing';
4+
import { array } from '@ember/helper';
5+
import { htmlSafe } from '@ember/template';
6+
import eq from 'ember-truth-helpers/helpers/eq';
7+
import { on } from '@ember/modifier';
8+
import Search from './search';
9+
10+
const SearchResultGroupHeader = <template>
11+
<div class='search-results--group-header' ...attributes>
12+
{{yield}}
13+
</div>
14+
</template>;
15+
16+
const SearchResultLinkContents = <template>
17+
<span class='screen-reader-text'>{{@groupName}}</span>
18+
{{htmlSafe @result._highlightResult.hierarchy.lvl2.value}}
19+
{{! Do these ever display in API Docs? }}
20+
{{#if @result._highlightResult.hierarchy.lvl3}}
21+
<span aria-hidden='true'> › </span>
22+
{{htmlSafe @result._highlightResult.hierarchy.lvl3.value}}
23+
{{/if}}
24+
{{#if @result._highlightResult.hierarchy.lvl4}}
25+
<span aria-hidden='true'> › </span>
26+
{{htmlSafe @result._highlightResult.hierarchy.lvl4.value}}
27+
{{/if}}
28+
</template>;
29+
30+
const SearchResult = class SearchResult extends Component {
31+
@service router;
32+
33+
get module() {
34+
if (this.args.result?.project) {
35+
return this.args.result?.project;
36+
}
37+
let module = this.args.result?.module;
38+
if (module.includes('ember-data')) {
39+
return 'ember-data';
40+
}
41+
return 'ember';
42+
}
43+
44+
get version() {
45+
let versionTag = (this.args.result?._tags ?? []).find(
46+
(_tag) => _tag.indexOf('version:') > -1,
47+
);
48+
let versionSegments = versionTag.replace('version:', '').split('.');
49+
return `${versionSegments[0]}.${versionSegments[1]}`;
50+
}
51+
52+
urlForClass = (result) => {
53+
return `${this.router.urlFor('project-version.classes.class', this.module, this.version, result.class)}#${result.name}`;
54+
};
55+
56+
<template>
57+
<div class='search-results--result'>
58+
<div class='search-results--subcategory-column' aria-hidden='true'>
59+
{{#if (eq @groupPosition 0)}}
60+
{{@groupName}}
61+
{{/if}}
62+
</div>
63+
<div class='search-results--content'>
64+
{{#if @result.static}}
65+
<LinkTo
66+
@route='project-version.functions.function'
67+
@models={{array
68+
this.module
69+
this.version
70+
@result.class
71+
@result.name
72+
}}
73+
{{on 'click' @closeMenu}}
74+
data-test-search-result
75+
>
76+
<SearchResultLinkContents
77+
@result={{@result}}
78+
@groupName={{@groupName}}
79+
/>
80+
</LinkTo>
81+
{{else}}
82+
<a href='{{this.urlForClass @result}}' data-test-search-result>
83+
<SearchResultLinkContents
84+
@result={{@result}}
85+
@groupName={{@groupName}}
86+
/>
87+
</a>
88+
{{/if}}
89+
</div>
90+
</div>
91+
</template>
92+
};
93+
94+
const SearchResults = class SearchBox extends Component {
95+
get groupedResults() {
96+
let results = this.args.results;
97+
if (!results.length) {
98+
return {};
99+
}
100+
101+
const lvl0Group = results.reduce((previous, current) => {
102+
// Remap all lowercase usages of 'guides' to 'Guides'
103+
let lvl0 = current?.hierarchy?.lvl0;
104+
// If lvl0 doesn't exist in the resulting object, create the array
105+
if (!previous[lvl0]) {
106+
previous[lvl0] = [];
107+
}
108+
// Insert the current item into the resulting object.
109+
previous[lvl0].push(current);
110+
return previous;
111+
}, {});
112+
113+
/*
114+
lvl0Group = {
115+
lvl0key: algoliaHit
116+
}
117+
*/
118+
// https://www.algolia.com/doc/guides/building-search-ui/ui-and-ux-patterns/highlighting-snippeting/js/#response-information
119+
// Iterate over every lvl0 group, group by lvl1
120+
return Object.keys(lvl0Group).reduce((lvl0Result, lvl0Key) => {
121+
// Inject lvl1 grouped results into lvl0
122+
lvl0Result[lvl0Key] = lvl0Group[lvl0Key].reduce(
123+
(lvl1Result, lvl1Item) => {
124+
// lvl1 is sometimes null. Normalise to a string.
125+
const lvl1Value = lvl1Item?.hierarchy?.lvl1;
126+
const lvl1Key = lvl1Value ? lvl1Value : lvl0Key;
127+
128+
if (!lvl1Result[lvl1Key]) {
129+
lvl1Result[lvl1Key] = [];
130+
}
131+
132+
lvl1Result[lvl1Key].push(lvl1Item);
133+
return lvl1Result;
134+
},
135+
{},
136+
);
137+
138+
return lvl0Result;
139+
}, {});
140+
}
141+
142+
<template>
143+
{{#each-in this.groupedResults as |lvl0section _lvl0results|}}
144+
<SearchResultGroupHeader aria-hidden='true'>
145+
{{lvl0section}}
146+
</SearchResultGroupHeader>
147+
148+
{{#each-in _lvl0results as |lvl1section _lvl1results|}}
149+
{{#each _lvl1results as |result index|}}
150+
<SearchResult
151+
@result={{result}}
152+
@groupName={{lvl1section}}
153+
@groupPosition={{index}}
154+
@closeMenu={{@closeMenu}}
155+
data-test-search-result
156+
/>
157+
{{/each}}
158+
{{/each-in}}
159+
{{/each-in}}
160+
</template>
161+
};
162+
163+
export default class ApiSearch extends Component {
164+
@service('search') searchService;
165+
@service('project') projectService;
166+
167+
<template>
168+
<Search @searchService={{this.searchService}}>
169+
<:searchInputScreenReaderLabel>Search v{{this.projectService.version}}
170+
of the API Docs. Results will update as you type.</:searchInputScreenReaderLabel>
171+
<:instructions>Type to search v{{this.projectService.version}}
172+
of the API Docs</:instructions>
173+
<:results as |results closeMenu|>
174+
<SearchResults @results={{results}} @closeMenu={{closeMenu}} />
175+
</:results>
176+
</Search>
177+
</template>
178+
}

app/components/search-input.hbs

Lines changed: 0 additions & 33 deletions
This file was deleted.

app/components/search-input.js

Lines changed: 0 additions & 72 deletions
This file was deleted.

app/components/search-input/dropdown-header.hbs

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/components/search-input/dropdown-result.hbs

Lines changed: 0 additions & 57 deletions
This file was deleted.

app/components/search-input/dropdown-result.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)