Skip to content

Commit c126039

Browse files
author
Dobromir Hristov
authored
Show group markers in search (#355)
closes rdar://90655322 * feat: show group markers in search * fix: make sure deprecated items stay hidden, even if a groupMarker labels deprecated items.
1 parent b7f9055 commit c126039

File tree

7 files changed

+328
-164
lines changed

7 files changed

+328
-164
lines changed

src/components/Navigator.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ export default {
176176
let items = [];
177177
const len = childrenNodes.length;
178178
let index;
179+
// reference to the last label node
180+
let groupMarkerNode = null;
179181
for (index = 0; index < len; index += 1) {
180182
// get the children
181183
const { children, ...node } = childrenNodes[index];
@@ -185,6 +187,13 @@ export default {
185187
node.uid = this.hashCode(`${parentUID}+${node.path}_${depth}_${index}`);
186188
// store the parent uid
187189
node.parent = parentUID;
190+
// store the current groupMarker reference
191+
if (node.type === TopicTypes.groupMarker) {
192+
groupMarkerNode = node;
193+
} else if (groupMarkerNode) {
194+
// push the current node to the group marker before it
195+
groupMarkerNode.childUIDs.push(node.uid);
196+
}
188197
// store which item it is
189198
node.index = index;
190199
// store how many siblings it has

src/components/Navigator/NavigatorCard.vue

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ export default {
251251
/** @type {NavigatorFlatItem[]} */
252252
nodesToRender: [],
253253
activeUID: null,
254-
resetScroll: false,
255254
store: QuickNavigationStore,
256255
lastFocusTarget: null,
257256
NO_RESULTS,
@@ -331,10 +330,11 @@ export default {
331330
FILTER_TAGS_TO_LABELS[tag] || ChangeNames[tag] || tag
332331
)),
333332
set(values) {
333+
// guard against accidental clearings
334+
if (!this.selectedTags.length && !values.length) return;
334335
this.selectedTags = values.map(label => (
335336
FILTER_LABELS_TO_TAGS[label] || ChangeNameToType[label] || label
336337
));
337-
this.resetScroll = true;
338338
},
339339
},
340340
filterPattern: ({ debouncedFilter }) => (!debouncedFilter
@@ -374,7 +374,7 @@ export default {
374374
*/
375375
filteredChildren({
376376
hasFilter, children, filterPattern, selectedTags,
377-
apiChangesObject, apiChanges, deprecatedHidden,
377+
apiChangesObject, apiChanges,
378378
}) {
379379
if (!hasFilter) return [];
380380
const tagsSet = new Set(selectedTags);
@@ -398,15 +398,13 @@ export default {
398398
}
399399
// find items, that have API changes
400400
const hasAPIChanges = apiChanges ? apiChangesObject[path] : true;
401-
// group markers are hidden when filtering, unless "Hide Deprecated" is ON.
402-
const isGroupMarker = deprecatedHidden ? false : type === TopicTypes.groupMarker;
403401
// make sure groupMarker's dont get matched
404-
return titleMatch && tagMatch && hasAPIChanges && !isGroupMarker;
402+
return titleMatch && tagMatch && hasAPIChanges;
405403
});
406404
},
407405
/**
408406
* Returns a Set of all nodes that match a filter, along with their parents.
409-
* @returns NavigatorFlatItem[]
407+
* @returns Set<NavigatorFlatItem>
410408
*/
411409
filteredChildrenUpToRootSet: ({ filteredChildren, getParents }) => new Set(
412410
filteredChildren.flatMap(({ uid }) => getParents(uid)),
@@ -416,26 +414,30 @@ export default {
416414
* This is used on both toggling, as well as on navigation and filtering.
417415
* @return {Object.<string, NavigatorFlatItem>}
418416
*/
419-
renderableChildNodesMap({ filteredChildrenUpToRootSet, childrenMap, hasFilter }) {
417+
renderableChildNodesMap({
418+
filteredChildrenUpToRootSet, childrenMap, hasFilter,
419+
getAllChildren, convertChildrenArrayToObject, removeDeprecated,
420+
}) {
420421
if (!hasFilter) return childrenMap;
421422
let all = [];
422423
// create a set of all matches and their parents
423424
filteredChildrenUpToRootSet.forEach((current) => {
424-
// if it has no children, just add it
425+
// if it's a plain end node, just add it
425426
if (!current.childUIDs.length) {
426427
all.push(current);
427428
return;
428429
}
430+
// check if none of the child items of this parent are matching
429431
const noChildrenMatch = !current.childUIDs.some(uid => (
430432
filteredChildrenUpToRootSet.has(childrenMap[uid])
431433
));
432-
all = all.concat(noChildrenMatch ? this.getAllChildren(current.uid) : current);
434+
// if no children are matching, add all to the list, otherwise just the current parent
435+
all = all.concat(noChildrenMatch ? removeDeprecated(getAllChildren(current.uid)) : current);
433436
});
434-
435-
return this.convertChildrenArrayToObject(all);
437+
return convertChildrenArrayToObject(all);
436438
},
437439
/**
438-
* Creates a computed for the two items, that the openNodes calc depends on
440+
* Creates a computed for the items, that the openNodes calc depends on
439441
*/
440442
nodeChangeDeps: ({
441443
filteredChildren, activePathChildren, debouncedFilter, selectedTags,
@@ -454,9 +456,7 @@ export default {
454456
* If we enable multiple tags, this should be an include instead.
455457
* @returns boolean
456458
*/
457-
deprecatedHidden: ({ selectedTags, debouncedFilter }) => (
458-
selectedTags[0] === HIDE_DEPRECATED_TAG && !debouncedFilter.length
459-
),
459+
deprecatedHidden: ({ selectedTags }) => selectedTags[0] === HIDE_DEPRECATED_TAG,
460460
apiChangesObject() {
461461
return this.apiChanges || {};
462462
},
@@ -486,16 +486,13 @@ export default {
486486
this.filter = '';
487487
this.debouncedFilter = '';
488488
this.selectedTags = [];
489-
this.resetScroll = true;
490489
},
491490
scrollToFocus() {
492491
this.$refs.scroller.scrollToItem(this.focusedIndex);
493492
},
494493
debounceInput: debounce(function debounceInput(value) {
495494
// store the new filter value
496495
this.debouncedFilter = value;
497-
// note to the component, that we want to reset the scroll
498-
this.resetScroll = true;
499496
// reset the last focus target
500497
this.lastFocusTarget = null;
501498
}, 500),
@@ -524,7 +521,11 @@ export default {
524521
// decide which items are open
525522
// if "Hide Deprecated" is picked, there is no filter,
526523
// or navigate to page while filtering, we open the items leading to the activeUID
527-
const nodes = this.deprecatedHidden || (pageChange && this.hasFilter) || !this.hasFilter
524+
const nodes = (
525+
(this.deprecatedHidden && !this.debouncedFilter.length)
526+
|| (pageChange && this.hasFilter)
527+
|| !this.hasFilter
528+
)
528529
? activePathChildren
529530
// get all parents of the current filter match, excluding it in the process
530531
: filteredChildren.flatMap(({ uid }) => this.getParents(uid).slice(0, -1));
@@ -698,6 +699,15 @@ export default {
698699
return (item.childUIDs || [])
699700
.map(child => this.childrenMap[child]);
700701
},
702+
/**
703+
* Removes deprecated items from a list
704+
* @param {NavigatorFlatItem[]} items
705+
* @returns {NavigatorFlatItem[]}
706+
*/
707+
removeDeprecated(items) {
708+
if (!this.deprecatedHidden) return items;
709+
return items.filter(({ deprecated }) => !deprecated);
710+
},
701711
/**
702712
* Stores all the nodes we should render at this point.
703713
* This gets called everytime you open/close a node,
@@ -860,7 +870,7 @@ export default {
860870
await waitFrames(1);
861871
if (!this.$refs.scroller) return;
862872
// if we are filtering, it makes more sense to scroll to top of list
863-
if (this.resetScroll) {
873+
if (this.hasFilter) {
864874
this.$refs.scroller.scrollToItem(0);
865875
return;
866876
}
@@ -951,7 +961,6 @@ export default {
951961
*/
952962
setActiveUID(uid) {
953963
this.activeUID = uid;
954-
this.resetScroll = false;
955964
},
956965
/**
957966
* Handles the `navigate` event from NavigatorCardItem, guarding from selecting an item,

src/components/Navigator/NavigatorCardItem.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export default {
163163
},
164164
computed: {
165165
isGroupMarker: ({ item: { type } }) => type === TopicTypes.groupMarker,
166-
isParent: ({ item }) => !!item.childUIDs.length,
166+
isParent: ({ item, isGroupMarker }) => !!item.childUIDs.length && !isGroupMarker,
167167
parentLabel: ({ item }) => `label-parent-${item.uid}`,
168168
siblingsLabel: ({ item }) => `label-${item.uid}`,
169169
usageLabel: ({ item }) => `usage-${item.uid}`,

tests/unit/components/Navigator.spec.js

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ jest.mock('docc-render/utils/throttle', () => jest.fn(v => v));
2222
const technology = {
2323
title: 'FooTechnology',
2424
children: [
25+
{
26+
title: 'Group Marker',
27+
type: TopicTypes.groupMarker,
28+
},
2529
{
2630
title: 'Child0',
2731
path: '/foo/child0',
2832
type: 'article',
2933
children: [
34+
{
35+
title: 'Group Marker, Child 0',
36+
type: TopicTypes.groupMarker,
37+
},
3038
{
3139
title: 'Child0_GrandChild0',
3240
path: '/foo/child0/grandchild0',
@@ -284,100 +292,127 @@ describe('Navigator', () => {
284292
it('flattens deeply nested children and provides them to the NavigatorCard', () => {
285293
const wrapper = createWrapper();
286294
expect(wrapper.find(NavigatorCard).props('children')).toEqual([
287-
// root
288295
{
289296
childUIDs: [
290-
745124197,
291-
746047719,
292-
746971241,
297+
551503844,
298+
-97593391,
293299
],
294300
depth: 0,
295301
index: 0,
296-
type: 'article',
297-
siblingsCount: 2,
302+
parent: INDEX_ROOT_KEY,
303+
siblingsCount: 3,
304+
title: 'Group Marker',
305+
type: 'groupMarker',
306+
uid: -196255993,
307+
},
308+
{
309+
childUIDs: [
310+
-361407047,
311+
1438225895,
312+
1439149417,
313+
1440072939,
314+
],
315+
depth: 0,
316+
index: 1,
298317
parent: INDEX_ROOT_KEY,
299318
path: '/foo/child0',
319+
siblingsCount: 3,
300320
title: 'Child0',
301-
uid: 551503843,
321+
type: 'article',
322+
uid: 551503844,
302323
},
303324
{
304-
childUIDs: [],
325+
childUIDs: [
326+
1438225895,
327+
1439149417,
328+
1440072939,
329+
],
305330
depth: 1,
306331
index: 0,
307-
type: 'tutorial',
308-
parent: 551503843,
309-
siblingsCount: 3,
332+
parent: 551503844,
333+
siblingsCount: 4,
334+
title: 'Group Marker, Child 0',
335+
type: 'groupMarker',
336+
uid: -361407047,
337+
},
338+
{
339+
childUIDs: [],
340+
depth: 1,
341+
index: 1,
342+
parent: 551503844,
310343
path: '/foo/child0/grandchild0',
344+
siblingsCount: 4,
311345
title: 'Child0_GrandChild0',
312-
uid: 745124197,
346+
type: 'tutorial',
347+
uid: 1438225895,
313348
},
314349
{
315350
childUIDs: [
316-
1489150959,
351+
305326087,
317352
],
318353
depth: 1,
319-
index: 1,
320-
type: 'tutorial',
321-
parent: 551503843,
322-
siblingsCount: 3,
354+
index: 2,
355+
parent: 551503844,
323356
path: '/foo/child0/grandchild1',
357+
siblingsCount: 4,
324358
title: 'Child0_GrandChild1',
325-
uid: 746047719,
359+
type: 'tutorial',
360+
uid: 1439149417,
326361
},
327362
{
328363
childUIDs: [],
329364
depth: 2,
330365
index: 0,
331-
type: 'tutorial',
332-
parent: 746047719,
333-
siblingsCount: 1,
366+
parent: 1439149417,
334367
path: '/foo/child0/grandchild0/greatgrandchild0',
368+
siblingsCount: 1,
335369
title: 'Child0_GrandChild0_GreatGrandChild0',
336-
uid: 1489150959,
370+
type: 'tutorial',
371+
uid: 305326087,
337372
},
338373
{
339374
childUIDs: [],
340375
depth: 1,
341-
index: 2,
342-
type: 'tutorial',
343-
parent: 551503843,
344-
siblingsCount: 3,
376+
index: 3,
377+
parent: 551503844,
345378
path: '/foo/child0/grandchild2',
379+
siblingsCount: 4,
346380
title: 'Child0_GrandChild2',
347-
uid: 746971241,
381+
type: 'tutorial',
382+
uid: 1440072939,
348383
},
349384
{
350385
childUIDs: [
351-
-134251586,
386+
-827353283,
352387
],
353388
depth: 0,
354-
index: 1,
355-
type: 'tutorial',
389+
index: 2,
356390
parent: INDEX_ROOT_KEY,
357-
siblingsCount: 2,
358391
path: '/foo/child1/',
392+
siblingsCount: 3,
359393
title: 'Child1',
360-
uid: -97593392,
394+
type: 'tutorial',
395+
uid: -97593391,
361396
},
362397
{
363398
childUIDs: [],
364399
depth: 1,
365400
index: 0,
366-
type: 'method',
367-
parent: -97593392,
368-
siblingsCount: 1,
401+
parent: -97593391,
369402
path: '/foo/child1/grandchild0',
403+
siblingsCount: 1,
370404
title: 'Child1_GrandChild0',
371-
uid: -134251586,
405+
type: 'method',
406+
uid: -827353283,
372407
},
373408
]);
374409
});
375410

376411
it('removes the `beta` flag from children, if the technology is a `beta`', () => {
377412
const technologyClone = clone(technology);
378413
technologyClone.beta = true;
379-
technologyClone.children[0].beta = true;
380-
technologyClone.children[0].children[0].beta = true;
414+
technologyClone.children[1].beta = true;
415+
technologyClone.children[1].children[0].beta = true;
381416
const wrapper = createWrapper({
382417
propsData: {
383418
technology: technologyClone,
@@ -388,12 +423,12 @@ describe('Navigator', () => {
388423

389424
it('removes the `beta` flag from children, if the parent is a `beta`', () => {
390425
const technologyClone = clone(technology);
391-
technologyClone.children[0].beta = true;
392-
technologyClone.children[0].children[0].beta = true;
426+
technologyClone.children[1].beta = true;
427+
technologyClone.children[1].children[1].beta = true;
393428
// case where the direct parent is NOT `Beta`, but an ancestor is
394-
technologyClone.children[0].children[1].children[0].beta = true;
429+
technologyClone.children[1].children[2].children[0].beta = true;
395430
// set an end node as beta
396-
technologyClone.children[1].children[0].beta = true;
431+
technologyClone.children[2].children[0].beta = true;
397432
const wrapper = createWrapper({
398433
propsData: {
399434
technology: technologyClone,

0 commit comments

Comments
 (0)