@internetarchive/collection-browser 1.6.0 → 1.7.1-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/collection-browser.d.ts +13 -0
- package/dist/src/collection-browser.js +62 -5
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/models.d.ts +2 -1
- package/dist/src/models.js +5 -1
- package/dist/src/models.js.map +1 -1
- package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +9 -0
- package/dist/src/sort-filter-bar/sort-filter-bar.js +62 -24
- package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
- package/dist/src/tiles/list/tile-list.js +7 -3
- package/dist/src/tiles/list/tile-list.js.map +1 -1
- package/dist/test/collection-browser.test.js +60 -2
- package/dist/test/collection-browser.test.js.map +1 -1
- package/dist/test/mocks/mock-search-responses.d.ts +2 -0
- package/dist/test/mocks/mock-search-responses.js +84 -0
- package/dist/test/mocks/mock-search-responses.js.map +1 -1
- package/dist/test/mocks/mock-search-service.js +6 -3
- package/dist/test/mocks/mock-search-service.js.map +1 -1
- package/package.json +3 -3
- package/src/collection-browser.ts +64 -4
- package/src/models.ts +6 -2
- package/src/sort-filter-bar/sort-filter-bar.ts +73 -26
- package/src/tiles/list/tile-list.ts +7 -3
- package/test/collection-browser.test.ts +79 -2
- package/test/mocks/mock-search-responses.ts +92 -0
- package/test/mocks/mock-search-service.ts +7 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getMockSuccessSingleResult, getMockSuccessMultipleResults, getMockSuccessSingleResultWithSort, getMockSuccessLoggedInResult, getMockSuccessNoPreviewResult, getMockSuccessLoggedInAndNoPreviewResult, getMockSuccessWithYearHistogramAggs, getMockSuccessMultiLineDescription, getMockSuccessFirstTitleResult, getMockSuccessFirstCreatorResult, getMockErrorResult, getMockMalformedResult, getMockSuccessWithCollectionTitles, getMockSuccessWithCollectionAggregations, getMockSuccessExtraQuotedHref, } from './mock-search-responses';
|
|
1
|
+
import { getMockSuccessSingleResult, getMockSuccessMultipleResults, getMockSuccessSingleResultWithSort, getMockSuccessLoggedInResult, getMockSuccessNoPreviewResult, getMockSuccessLoggedInAndNoPreviewResult, getMockSuccessWithYearHistogramAggs, getMockSuccessMultiLineDescription, getMockSuccessFirstTitleResult, getMockSuccessFirstCreatorResult, getMockErrorResult, getMockMalformedResult, getMockSuccessWithCollectionTitles, getMockSuccessWithCollectionAggregations, getMockSuccessExtraQuotedHref, getMockSuccessWithDefaultSort, getMockSuccessWithConciseDefaultSort, } from './mock-search-responses';
|
|
2
2
|
const responses = {
|
|
3
3
|
'single-result': getMockSuccessSingleResult,
|
|
4
4
|
years: getMockSuccessWithYearHistogramAggs,
|
|
@@ -11,6 +11,8 @@ const responses = {
|
|
|
11
11
|
'collection-titles': getMockSuccessWithCollectionTitles,
|
|
12
12
|
'collection-aggregations': getMockSuccessWithCollectionAggregations,
|
|
13
13
|
'extra-quoted-href': getMockSuccessExtraQuotedHref,
|
|
14
|
+
'default-sort': getMockSuccessWithDefaultSort,
|
|
15
|
+
'default-sort-concise': getMockSuccessWithConciseDefaultSort,
|
|
14
16
|
error: getMockErrorResult,
|
|
15
17
|
malformed: getMockMalformedResult,
|
|
16
18
|
};
|
|
@@ -21,7 +23,7 @@ export class MockSearchService {
|
|
|
21
23
|
this.resultsSpy = resultsSpy;
|
|
22
24
|
}
|
|
23
25
|
async search(params, searchType) {
|
|
24
|
-
var _a;
|
|
26
|
+
var _a, _b;
|
|
25
27
|
this.searchParams = params;
|
|
26
28
|
this.searchType = searchType;
|
|
27
29
|
if (this.asyncResponse) {
|
|
@@ -30,7 +32,8 @@ export class MockSearchService {
|
|
|
30
32
|
setTimeout(res, this.asyncResponseDelay);
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
|
-
const
|
|
35
|
+
const responseKey = (_a = (this.searchParams.query || this.searchParams.pageTarget)) !== null && _a !== void 0 ? _a : '';
|
|
36
|
+
const resultFn = (_b = responses[responseKey]) !== null && _b !== void 0 ? _b : getMockSuccessMultipleResults;
|
|
34
37
|
let result = resultFn();
|
|
35
38
|
// with-sort query has special handling
|
|
36
39
|
if (this.searchParams.query === 'with-sort') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-search-service.js","sourceRoot":"","sources":["../../../test/mocks/mock-search-service.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,kCAAkC,EAClC,4BAA4B,EAC5B,6BAA6B,EAC7B,wCAAwC,EACxC,mCAAmC,EACnC,kCAAkC,EAClC,8BAA8B,EAC9B,gCAAgC,EAChC,kBAAkB,EAClB,sBAAsB,EACtB,kCAAkC,EAClC,wCAAwC,EACxC,6BAA6B,
|
|
1
|
+
{"version":3,"file":"mock-search-service.js","sourceRoot":"","sources":["../../../test/mocks/mock-search-service.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,kCAAkC,EAClC,4BAA4B,EAC5B,6BAA6B,EAC7B,wCAAwC,EACxC,mCAAmC,EACnC,kCAAkC,EAClC,8BAA8B,EAC9B,gCAAgC,EAChC,kBAAkB,EAClB,sBAAsB,EACtB,kCAAkC,EAClC,wCAAwC,EACxC,6BAA6B,EAC7B,6BAA6B,EAC7B,oCAAoC,GACrC,MAAM,yBAAyB,CAAC;AAEjC,MAAM,SAAS,GAGX;IACF,eAAe,EAAE,0BAA0B;IAC3C,KAAK,EAAE,mCAAmC;IAC1C,wBAAwB,EAAE,kCAAkC;IAC5D,QAAQ,EAAE,4BAA4B;IACtC,YAAY,EAAE,6BAA6B;IAC3C,qBAAqB,EAAE,wCAAwC;IAC/D,aAAa,EAAE,8BAA8B;IAC7C,eAAe,EAAE,gCAAgC;IACjD,mBAAmB,EAAE,kCAAkC;IACvD,yBAAyB,EAAE,wCAAwC;IACnE,mBAAmB,EAAE,6BAA6B;IAClD,cAAc,EAAE,6BAA6B;IAC7C,sBAAsB,EAAE,oCAAoC;IAC5D,KAAK,EAAE,kBAAkB;IACzB,SAAS,EAAE,sBAAsB;CAClC,CAAC;AAEF,MAAM,OAAO,iBAAiB;IAW5B,YAAY,EACV,aAAa,GAAG,KAAK,EACrB,kBAAkB,GAAG,CAAC,EACtB,UAAU,GAAG,GAAG,EAAE,GAAE,CAAC,GACtB,GAAG,EAAE;QACJ,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC7C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,MAAM,CACV,MAAoB,EACpB,UAAsB;;QAEtB,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC;QAC3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAE7B,IAAI,IAAI,CAAC,aAAa,EAAE;YACtB,iCAAiC;YACjC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE;gBACtB,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC;SACJ;QAED,MAAM,WAAW,GACf,MAAA,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,mCAAI,EAAE,CAAC;QAClE,MAAM,QAAQ,GACZ,MAAA,SAAS,CAAC,WAAW,CAAC,mCAAI,6BAA6B,CAAC;QAC1D,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;QAExB,uCAAuC;QACvC,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,KAAK,WAAW,EAAE;YAC3C,MAAM,GAAG,kCAAkC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;SAC9D;QAED,uCAAuC;QACvC,IAAI,MAAM,CAAC,OAAO,EAAE;YACjB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAwB,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;SACnE;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["import type { Result } from '@internetarchive/result-type';\nimport type {\n SearchParams,\n SearchResponse,\n SearchServiceInterface,\n SearchServiceError,\n SearchType,\n} from '@internetarchive/search-service';\nimport {\n getMockSuccessSingleResult,\n getMockSuccessMultipleResults,\n getMockSuccessSingleResultWithSort,\n getMockSuccessLoggedInResult,\n getMockSuccessNoPreviewResult,\n getMockSuccessLoggedInAndNoPreviewResult,\n getMockSuccessWithYearHistogramAggs,\n getMockSuccessMultiLineDescription,\n getMockSuccessFirstTitleResult,\n getMockSuccessFirstCreatorResult,\n getMockErrorResult,\n getMockMalformedResult,\n getMockSuccessWithCollectionTitles,\n getMockSuccessWithCollectionAggregations,\n getMockSuccessExtraQuotedHref,\n getMockSuccessWithDefaultSort,\n getMockSuccessWithConciseDefaultSort,\n} from './mock-search-responses';\n\nconst responses: Record<\n string,\n () => Result<SearchResponse, SearchServiceError>\n> = {\n 'single-result': getMockSuccessSingleResult,\n years: getMockSuccessWithYearHistogramAggs,\n 'multi-line-description': getMockSuccessMultiLineDescription,\n loggedin: getMockSuccessLoggedInResult,\n 'no-preview': getMockSuccessNoPreviewResult,\n 'loggedin-no-preview': getMockSuccessLoggedInAndNoPreviewResult,\n 'first-title': getMockSuccessFirstTitleResult,\n 'first-creator': getMockSuccessFirstCreatorResult,\n 'collection-titles': getMockSuccessWithCollectionTitles,\n 'collection-aggregations': getMockSuccessWithCollectionAggregations,\n 'extra-quoted-href': getMockSuccessExtraQuotedHref,\n 'default-sort': getMockSuccessWithDefaultSort,\n 'default-sort-concise': getMockSuccessWithConciseDefaultSort,\n error: getMockErrorResult,\n malformed: getMockMalformedResult,\n};\n\nexport class MockSearchService implements SearchServiceInterface {\n searchParams?: SearchParams;\n\n searchType?: SearchType;\n\n asyncResponse: boolean;\n\n asyncResponseDelay: number;\n\n resultsSpy: Function;\n\n constructor({\n asyncResponse = false,\n asyncResponseDelay = 0,\n resultsSpy = () => {},\n } = {}) {\n this.asyncResponse = asyncResponse;\n this.asyncResponseDelay = asyncResponseDelay;\n this.resultsSpy = resultsSpy;\n }\n\n async search(\n params: SearchParams,\n searchType: SearchType\n ): Promise<Result<SearchResponse, SearchServiceError>> {\n this.searchParams = params;\n this.searchType = searchType;\n\n if (this.asyncResponse) {\n // Add an artificial 1-tick delay\n await new Promise(res => {\n setTimeout(res, this.asyncResponseDelay);\n });\n }\n\n const responseKey =\n (this.searchParams.query || this.searchParams.pageTarget) ?? '';\n const resultFn: () => Result<SearchResponse, SearchServiceError> =\n responses[responseKey] ?? getMockSuccessMultipleResults;\n let result = resultFn();\n\n // with-sort query has special handling\n if (this.searchParams.query === 'with-sort') {\n result = getMockSuccessSingleResultWithSort(this.resultsSpy);\n }\n\n // Apply any uid param from the request\n if (result.success) {\n (result.success.request.clientParameters as any).uid = params.uid;\n }\n\n return result;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "The Internet Archive Collection Browser.",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"author": "Internet Archive",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.7.1-alpha.0",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
9
9
|
"scripts": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"types": "dist/index.d.ts",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@internetarchive/analytics-manager": "^0.1.2",
|
|
26
|
-
"@internetarchive/collection-name-cache": "^0.2.
|
|
26
|
+
"@internetarchive/collection-name-cache": "^0.2.13-alpha.0",
|
|
27
27
|
"@internetarchive/feature-feedback": "^0.1.4",
|
|
28
28
|
"@internetarchive/field-parsers": "^0.1.3",
|
|
29
29
|
"@internetarchive/histogram-date-range": "^1.1.0",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@internetarchive/infinite-scroller": "^0.1.4",
|
|
33
33
|
"@internetarchive/local-cache": "^0.2.1",
|
|
34
34
|
"@internetarchive/modal-manager": "^0.2.8",
|
|
35
|
-
"@internetarchive/search-service": "^1.1.0",
|
|
35
|
+
"@internetarchive/search-service": "^1.1.1-alpha.0",
|
|
36
36
|
"@internetarchive/shared-resize-observer": "^0.2.0",
|
|
37
37
|
"@lit/localize": "^0.11.2",
|
|
38
38
|
"dompurify": "^2.3.6",
|
|
@@ -57,6 +57,8 @@ import {
|
|
|
57
57
|
PrefixFilterCounts,
|
|
58
58
|
prefixFilterAggregationKeys,
|
|
59
59
|
FacetEventDetails,
|
|
60
|
+
MetadataFieldToSortField,
|
|
61
|
+
MetadataSortField,
|
|
60
62
|
} from './models';
|
|
61
63
|
import {
|
|
62
64
|
RestorationStateHandlerInterface,
|
|
@@ -100,7 +102,7 @@ export class CollectionBrowser
|
|
|
100
102
|
|
|
101
103
|
@property({ type: Object }) sortParam: SortParam | null = null;
|
|
102
104
|
|
|
103
|
-
@property({ type: String }) selectedSort: SortField = SortField.
|
|
105
|
+
@property({ type: String }) selectedSort: SortField = SortField.default;
|
|
104
106
|
|
|
105
107
|
@property({ type: String }) selectedTitleFilter: string | null = null;
|
|
106
108
|
|
|
@@ -194,6 +196,11 @@ export class CollectionBrowser
|
|
|
194
196
|
|
|
195
197
|
@state() private contentWidth?: number;
|
|
196
198
|
|
|
199
|
+
@state() private defaultSortField: Exclude<SortField, SortField.default> =
|
|
200
|
+
SortField.relevance;
|
|
201
|
+
|
|
202
|
+
@state() private defaultSortDirection: SortDirection | null = null;
|
|
203
|
+
|
|
197
204
|
@state() private placeholderType: PlaceholderType = null;
|
|
198
205
|
|
|
199
206
|
@state() private prefixFilterCountMap: Partial<
|
|
@@ -351,7 +358,7 @@ export class CollectionBrowser
|
|
|
351
358
|
if (sort) {
|
|
352
359
|
this.sortParam = null;
|
|
353
360
|
this.sortDirection = null;
|
|
354
|
-
this.selectedSort = SortField.
|
|
361
|
+
this.selectedSort = SortField.default;
|
|
355
362
|
}
|
|
356
363
|
}
|
|
357
364
|
|
|
@@ -553,8 +560,11 @@ export class CollectionBrowser
|
|
|
553
560
|
private get sortFilterBarTemplate() {
|
|
554
561
|
return html`
|
|
555
562
|
<sort-filter-bar
|
|
563
|
+
.defaultSortField=${this.defaultSortField}
|
|
564
|
+
.defaultSortDirection=${this.defaultSortDirection}
|
|
556
565
|
.selectedSort=${this.selectedSort}
|
|
557
566
|
.sortDirection=${this.sortDirection}
|
|
567
|
+
.showRelevance=${this.isRelevanceSortAvailable}
|
|
558
568
|
.displayMode=${this.displayMode}
|
|
559
569
|
.selectedTitleFilter=${this.selectedTitleFilter}
|
|
560
570
|
.selectedCreatorFilter=${this.selectedCreatorFilter}
|
|
@@ -598,7 +608,7 @@ export class CollectionBrowser
|
|
|
598
608
|
}
|
|
599
609
|
|
|
600
610
|
private selectedSortChanged(): void {
|
|
601
|
-
if (this.selectedSort
|
|
611
|
+
if ([SortField.default, SortField.relevance].includes(this.selectedSort)) {
|
|
602
612
|
this.sortParam = null;
|
|
603
613
|
return;
|
|
604
614
|
}
|
|
@@ -1195,6 +1205,11 @@ export class CollectionBrowser
|
|
|
1195
1205
|
this.infiniteScroller.reload();
|
|
1196
1206
|
}
|
|
1197
1207
|
|
|
1208
|
+
if (this.withinCollection && this.baseQuery?.trim()) {
|
|
1209
|
+
this.defaultSortField = SortField.relevance;
|
|
1210
|
+
this.defaultSortDirection = null;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1198
1213
|
if (!this.initialQueryChangeHappened && this.initialPageNumber > 1) {
|
|
1199
1214
|
this.scrollToPage(this.initialPageNumber);
|
|
1200
1215
|
}
|
|
@@ -1238,7 +1253,7 @@ export class CollectionBrowser
|
|
|
1238
1253
|
this.displayMode = restorationState.displayMode;
|
|
1239
1254
|
if (restorationState.searchType != null)
|
|
1240
1255
|
this.searchType = restorationState.searchType;
|
|
1241
|
-
this.selectedSort = restorationState.selectedSort ?? SortField.
|
|
1256
|
+
this.selectedSort = restorationState.selectedSort ?? SortField.default;
|
|
1242
1257
|
this.sortDirection = restorationState.sortDirection ?? null;
|
|
1243
1258
|
this.selectedTitleFilter = restorationState.selectedTitleFilter ?? null;
|
|
1244
1259
|
this.selectedCreatorFilter = restorationState.selectedCreatorFilter ?? null;
|
|
@@ -1612,6 +1627,14 @@ export class CollectionBrowser
|
|
|
1612
1627
|
});
|
|
1613
1628
|
}
|
|
1614
1629
|
|
|
1630
|
+
/**
|
|
1631
|
+
* Whether sorting by relevance makes sense for the current state.
|
|
1632
|
+
* Currently equivalent to having a non-empty query.
|
|
1633
|
+
*/
|
|
1634
|
+
private get isRelevanceSortAvailable(): boolean {
|
|
1635
|
+
return !!this.baseQuery?.trim();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1615
1638
|
/**
|
|
1616
1639
|
* Whether a search may be performed in the current state of the component.
|
|
1617
1640
|
* This is only true if the search service is defined, and either
|
|
@@ -1757,6 +1780,10 @@ export class CollectionBrowser
|
|
|
1757
1780
|
|
|
1758
1781
|
if (this.withinCollection) {
|
|
1759
1782
|
this.collectionInfo = success.response.collectionExtraInfo;
|
|
1783
|
+
|
|
1784
|
+
// For collections, we want the UI to respect the default sort option
|
|
1785
|
+
// which can be specified in metadata, or otherwise assumed to be week:desc
|
|
1786
|
+
this.applyDefaultCollectionSort(success.response.collectionExtraInfo);
|
|
1760
1787
|
}
|
|
1761
1788
|
|
|
1762
1789
|
const { results, collectionTitles } = success.response;
|
|
@@ -1804,6 +1831,39 @@ export class CollectionBrowser
|
|
|
1804
1831
|
this.collectionNameCache?.preloadIdentifiers(collectionIdsArray);
|
|
1805
1832
|
}
|
|
1806
1833
|
|
|
1834
|
+
/**
|
|
1835
|
+
* Applies any default sort option for the current collection, by checking
|
|
1836
|
+
* for one in the collection's metadata. If none is found, defaults to sorting
|
|
1837
|
+
* descending by weekly views.
|
|
1838
|
+
*/
|
|
1839
|
+
private applyDefaultCollectionSort(collectionInfo?: CollectionExtraInfo) {
|
|
1840
|
+
if (this.baseQuery) {
|
|
1841
|
+
// If there's a query set, then we default to relevance sorting regardless of
|
|
1842
|
+
// the collection metadata-specified sort.
|
|
1843
|
+
this.defaultSortField = SortField.relevance;
|
|
1844
|
+
this.defaultSortDirection = null;
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
const defaultSort: string =
|
|
1849
|
+
collectionInfo?.public_metadata?.['sort-by'] ?? '-week';
|
|
1850
|
+
|
|
1851
|
+
// Account for both -field and field:dir formats
|
|
1852
|
+
let [field, dir] = defaultSort.split(':');
|
|
1853
|
+
if (field.startsWith('-')) {
|
|
1854
|
+
field = field.slice(1);
|
|
1855
|
+
dir = 'desc';
|
|
1856
|
+
} else if (!['asc', 'desc'].includes(dir)) {
|
|
1857
|
+
dir = 'asc';
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
const sortField = MetadataFieldToSortField[field as MetadataSortField];
|
|
1861
|
+
if (sortField && sortField !== SortField.default) {
|
|
1862
|
+
this.defaultSortField = sortField;
|
|
1863
|
+
this.defaultSortDirection = dir as SortDirection;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1807
1867
|
/**
|
|
1808
1868
|
* This is useful for determining whether we need to reload the scroller.
|
|
1809
1869
|
*
|
package/src/models.ts
CHANGED
|
@@ -55,6 +55,7 @@ export type CollectionBrowserContext = 'collection' | 'search';
|
|
|
55
55
|
* The sort fields shown in the sort filter bar
|
|
56
56
|
*/
|
|
57
57
|
export enum SortField {
|
|
58
|
+
'default' = 'default',
|
|
58
59
|
'relevance' = 'relevance',
|
|
59
60
|
'alltimeview' = 'alltimeview',
|
|
60
61
|
'weeklyview' = 'weeklyview',
|
|
@@ -87,6 +88,7 @@ export type URLSortField = MetadataSortField | 'title' | 'creator';
|
|
|
87
88
|
export const SortFieldDisplayName: {
|
|
88
89
|
[key in SortField]: string;
|
|
89
90
|
} = {
|
|
91
|
+
default: '', // Use the default sorting option for the current page context, if none has been selected
|
|
90
92
|
relevance: 'Relevance',
|
|
91
93
|
alltimeview: 'All-time views',
|
|
92
94
|
weeklyview: 'Weekly views',
|
|
@@ -99,9 +101,10 @@ export const SortFieldDisplayName: {
|
|
|
99
101
|
};
|
|
100
102
|
|
|
101
103
|
export const DefaultSortDirection: {
|
|
102
|
-
[key in SortField]: SortDirection;
|
|
104
|
+
[key in SortField]: SortDirection | null;
|
|
103
105
|
} = {
|
|
104
|
-
|
|
106
|
+
default: null,
|
|
107
|
+
relevance: null, // Sort direction is disabled entirely for relevance sort (user can't click the button)
|
|
105
108
|
alltimeview: 'desc',
|
|
106
109
|
weeklyview: 'desc',
|
|
107
110
|
title: 'asc',
|
|
@@ -118,6 +121,7 @@ export const DefaultSortDirection: {
|
|
|
118
121
|
export const SortFieldToMetadataField: {
|
|
119
122
|
[key in SortField]: MetadataSortField | null;
|
|
120
123
|
} = {
|
|
124
|
+
default: null,
|
|
121
125
|
relevance: null,
|
|
122
126
|
alltimeview: 'downloads',
|
|
123
127
|
weeklyview: 'week',
|
|
@@ -42,11 +42,20 @@ export class SortFilterBar
|
|
|
42
42
|
/** Which display mode the tiles are being rendered with (grid/list-detail/list-compact) */
|
|
43
43
|
@property({ type: String }) displayMode?: CollectionDisplayMode;
|
|
44
44
|
|
|
45
|
+
/** The default sort direction to use if none is set */
|
|
46
|
+
@property({ type: String }) defaultSortDirection: SortDirection | null = null;
|
|
47
|
+
|
|
48
|
+
/** The default sort field to use if none is set */
|
|
49
|
+
@property({ type: String }) defaultSortField: Exclude<
|
|
50
|
+
SortField,
|
|
51
|
+
SortField.default
|
|
52
|
+
> = SortField.relevance;
|
|
53
|
+
|
|
45
54
|
/** The current sort direction (asc/desc), or null if none is set */
|
|
46
55
|
@property({ type: String }) sortDirection: SortDirection | null = null;
|
|
47
56
|
|
|
48
57
|
/** The field currently being sorted on (e.g., 'title'). Defaults to relevance. */
|
|
49
|
-
@property({ type: String }) selectedSort: SortField = SortField.
|
|
58
|
+
@property({ type: String }) selectedSort: SortField = SortField.default;
|
|
50
59
|
|
|
51
60
|
/** The currently selected title letter filter, or null if none is set */
|
|
52
61
|
@property({ type: String }) selectedTitleFilter: string | null = null;
|
|
@@ -144,7 +153,7 @@ export class SortFilterBar
|
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
if (changed.has('selectedSort') && this.sortDirection === null) {
|
|
147
|
-
this.sortDirection = DefaultSortDirection[this.
|
|
156
|
+
this.sortDirection = DefaultSortDirection[this.finalizedSortField];
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
if (changed.has('selectedTitleFilter') && this.selectedTitleFilter) {
|
|
@@ -265,8 +274,8 @@ export class SortFilterBar
|
|
|
265
274
|
return html`
|
|
266
275
|
<button
|
|
267
276
|
class="sort-direction-selector"
|
|
268
|
-
?disabled=${this.
|
|
269
|
-
@click=${this.
|
|
277
|
+
?disabled=${this.finalizedSortField === SortField.relevance}
|
|
278
|
+
@click=${this.handleSortDirectionClicked}
|
|
270
279
|
>
|
|
271
280
|
<span class="sr-only">${srLabel}</span>
|
|
272
281
|
${this.sortDirectionIcon}
|
|
@@ -277,14 +286,14 @@ export class SortFilterBar
|
|
|
277
286
|
/** Template to render the sort direction button's icon in the correct current state */
|
|
278
287
|
private get sortDirectionIcon(): TemplateResult {
|
|
279
288
|
// For relevance sort, show a fully disabled icon
|
|
280
|
-
if (this.
|
|
289
|
+
if (this.finalizedSortField === SortField.relevance) {
|
|
281
290
|
return html`<div class="sort-direction-icon">${sortDisabledIcon}</div>`;
|
|
282
291
|
}
|
|
283
292
|
|
|
284
293
|
// For all other sorts, show the ascending/descending direction
|
|
285
294
|
return html`
|
|
286
295
|
<div class="sort-direction-icon">
|
|
287
|
-
${this.
|
|
296
|
+
${this.finalizedSortDirection === 'asc' ? sortUpIcon : sortDownIcon}
|
|
288
297
|
</div>
|
|
289
298
|
`;
|
|
290
299
|
}
|
|
@@ -297,25 +306,25 @@ export class SortFilterBar
|
|
|
297
306
|
class=${this.mobileSelectorVisible ? 'hidden' : 'visible'}
|
|
298
307
|
>
|
|
299
308
|
<ul id="desktop-sort-selector">
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
309
|
+
${this.showRelevance
|
|
310
|
+
? html`<li>
|
|
311
|
+
${this.getSortDisplayOption(SortField.relevance, {
|
|
303
312
|
onClick: () => {
|
|
304
313
|
this.dropdownBackdropVisible = false;
|
|
305
|
-
if (this.
|
|
314
|
+
if (this.finalizedSortField !== SortField.relevance) {
|
|
306
315
|
this.clearAlphaBarFilters();
|
|
307
316
|
this.setSelectedSort(SortField.relevance);
|
|
308
317
|
}
|
|
309
318
|
},
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
|
|
319
|
+
})}
|
|
320
|
+
</li>`
|
|
321
|
+
: nothing}
|
|
313
322
|
<li>${this.viewsDropdownTemplate}</li>
|
|
314
323
|
<li>
|
|
315
324
|
${this.getSortDisplayOption(SortField.title, {
|
|
316
325
|
onClick: () => {
|
|
317
326
|
this.dropdownBackdropVisible = false;
|
|
318
|
-
if (this.
|
|
327
|
+
if (this.finalizedSortField !== SortField.title) {
|
|
319
328
|
this.alphaSelectorVisible = 'title';
|
|
320
329
|
this.selectedCreatorFilter = null;
|
|
321
330
|
this.setSelectedSort(SortField.title);
|
|
@@ -329,7 +338,7 @@ export class SortFilterBar
|
|
|
329
338
|
${this.getSortDisplayOption(SortField.creator, {
|
|
330
339
|
onClick: () => {
|
|
331
340
|
this.dropdownBackdropVisible = false;
|
|
332
|
-
if (this.
|
|
341
|
+
if (this.finalizedSortField !== SortField.creator) {
|
|
333
342
|
this.alphaSelectorVisible = 'creator';
|
|
334
343
|
this.selectedTitleFilter = null;
|
|
335
344
|
this.setSelectedSort(SortField.creator);
|
|
@@ -345,19 +354,24 @@ export class SortFilterBar
|
|
|
345
354
|
|
|
346
355
|
/** The template to render all the sort options in mobile view */
|
|
347
356
|
private get mobileSortSelectorTemplate() {
|
|
357
|
+
const isDisplayableField = (field: string) =>
|
|
358
|
+
field !== SortField.default &&
|
|
359
|
+
(field !== SortField.relevance || this.showRelevance);
|
|
360
|
+
|
|
348
361
|
return html`
|
|
349
362
|
<div
|
|
350
363
|
id="mobile-sort-container"
|
|
351
364
|
class=${this.mobileSelectorVisible ? 'visible' : 'hidden'}
|
|
352
365
|
>
|
|
353
366
|
${this.getSortDropdown({
|
|
354
|
-
displayName: html`${SortFieldDisplayName[this.
|
|
367
|
+
displayName: html`${SortFieldDisplayName[this.finalizedSortField] ??
|
|
368
|
+
'Relevance'}`,
|
|
355
369
|
id: 'mobile-dropdown',
|
|
356
370
|
selected: true,
|
|
357
|
-
dropdownOptions: Object.keys(SortField)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
selectedOption: this.
|
|
371
|
+
dropdownOptions: Object.keys(SortField)
|
|
372
|
+
.filter(field => isDisplayableField(field))
|
|
373
|
+
.map(field => this.getDropdownOption(field as SortField)),
|
|
374
|
+
selectedOption: this.finalizedSortField,
|
|
361
375
|
onOptionSelected: this.mobileSortChanged,
|
|
362
376
|
onDropdownClick: () => {
|
|
363
377
|
this.dropdownBackdropVisible = this.mobileDropdown.open;
|
|
@@ -392,7 +406,8 @@ export class SortFilterBar
|
|
|
392
406
|
onClick?: (e: Event) => void;
|
|
393
407
|
}
|
|
394
408
|
): TemplateResult {
|
|
395
|
-
const isSelected =
|
|
409
|
+
const isSelected =
|
|
410
|
+
options?.selected ?? this.finalizedSortField === sortField;
|
|
396
411
|
const displayName = options?.displayName ?? SortFieldDisplayName[sortField];
|
|
397
412
|
return html`
|
|
398
413
|
<button
|
|
@@ -653,7 +668,25 @@ export class SortFilterBar
|
|
|
653
668
|
|
|
654
669
|
/** Toggles the current sort direction between 'asc' and 'desc' */
|
|
655
670
|
private toggleSortDirection() {
|
|
656
|
-
this.setSortDirection(
|
|
671
|
+
this.setSortDirection(
|
|
672
|
+
this.finalizedSortDirection === 'desc' ? 'asc' : 'desc'
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private handleSortDirectionClicked(): void {
|
|
677
|
+
if (
|
|
678
|
+
!this.sortDirection &&
|
|
679
|
+
this.defaultSortField &&
|
|
680
|
+
this.defaultSortDirection
|
|
681
|
+
) {
|
|
682
|
+
// When the sort direction is merely defaulted (not set by the user), clicking
|
|
683
|
+
// the toggled button should "promote" the default sort to an explicitly-set one
|
|
684
|
+
// and then toggle it as usual.
|
|
685
|
+
this.selectedSort = this.defaultSortField;
|
|
686
|
+
this.sortDirection = this.defaultSortDirection;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
this.toggleSortDirection();
|
|
657
690
|
}
|
|
658
691
|
|
|
659
692
|
private setSelectedSort(sort: SortField) {
|
|
@@ -663,6 +696,20 @@ export class SortFilterBar
|
|
|
663
696
|
this.emitSortChangedEvent();
|
|
664
697
|
}
|
|
665
698
|
|
|
699
|
+
/** The current sort field, or the default one if no explicit sort is set */
|
|
700
|
+
private get finalizedSortField(): SortField {
|
|
701
|
+
return this.selectedSort === SortField.default
|
|
702
|
+
? this.defaultSortField
|
|
703
|
+
: this.selectedSort;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** The current sort direction, or the default one if no explicit direction is set */
|
|
707
|
+
private get finalizedSortDirection(): SortDirection | null {
|
|
708
|
+
return this.sortDirection === null
|
|
709
|
+
? this.defaultSortDirection
|
|
710
|
+
: this.sortDirection;
|
|
711
|
+
}
|
|
712
|
+
|
|
666
713
|
/**
|
|
667
714
|
* There are four date sort options.
|
|
668
715
|
*
|
|
@@ -680,7 +727,7 @@ export class SortFilterBar
|
|
|
680
727
|
SortField.datereviewed,
|
|
681
728
|
SortField.dateadded,
|
|
682
729
|
];
|
|
683
|
-
return dateSortFields.includes(this.
|
|
730
|
+
return dateSortFields.includes(this.finalizedSortField);
|
|
684
731
|
}
|
|
685
732
|
|
|
686
733
|
/**
|
|
@@ -698,7 +745,7 @@ export class SortFilterBar
|
|
|
698
745
|
SortField.alltimeview,
|
|
699
746
|
SortField.weeklyview,
|
|
700
747
|
];
|
|
701
|
-
return viewSortFields.includes(this.
|
|
748
|
+
return viewSortFields.includes(this.finalizedSortField);
|
|
702
749
|
}
|
|
703
750
|
|
|
704
751
|
/**
|
|
@@ -712,7 +759,7 @@ export class SortFilterBar
|
|
|
712
759
|
private get dateSortField(): string {
|
|
713
760
|
const defaultSort = SortFieldDisplayName[SortField.date];
|
|
714
761
|
const name = this.dateOptionSelected
|
|
715
|
-
? SortFieldDisplayName[this.
|
|
762
|
+
? SortFieldDisplayName[this.finalizedSortField] ?? defaultSort
|
|
716
763
|
: defaultSort;
|
|
717
764
|
return name;
|
|
718
765
|
}
|
|
@@ -728,7 +775,7 @@ export class SortFilterBar
|
|
|
728
775
|
private get viewSortField(): string {
|
|
729
776
|
const defaultSort = SortFieldDisplayName[SortField.weeklyview];
|
|
730
777
|
const name = this.viewOptionSelected
|
|
731
|
-
? SortFieldDisplayName[this.
|
|
778
|
+
? SortFieldDisplayName[this.finalizedSortField] ?? defaultSort
|
|
732
779
|
: defaultSort;
|
|
733
780
|
return name;
|
|
734
781
|
}
|
|
@@ -197,7 +197,7 @@ export class TileList extends BaseTileComponent {
|
|
|
197
197
|
${this.labelTemplate(msg('By'))}
|
|
198
198
|
${join(
|
|
199
199
|
map(this.model.creators, id => this.searchLink('creator', id)),
|
|
200
|
-
|
|
200
|
+
', '
|
|
201
201
|
)}
|
|
202
202
|
</div>
|
|
203
203
|
`;
|
|
@@ -262,7 +262,7 @@ export class TileList extends BaseTileComponent {
|
|
|
262
262
|
${this.labelTemplate(msg('Topics'))}
|
|
263
263
|
${join(
|
|
264
264
|
map(this.model.subjects, id => this.searchLink('subject', id)),
|
|
265
|
-
|
|
265
|
+
', '
|
|
266
266
|
)}
|
|
267
267
|
</div>
|
|
268
268
|
`;
|
|
@@ -275,7 +275,7 @@ export class TileList extends BaseTileComponent {
|
|
|
275
275
|
return html`
|
|
276
276
|
<div id="collections" class="metadata">
|
|
277
277
|
${this.labelTemplate(msg('Collections'))}
|
|
278
|
-
${join(this.collectionLinks,
|
|
278
|
+
${join(this.collectionLinks, ', ')}
|
|
279
279
|
</div>
|
|
280
280
|
`;
|
|
281
281
|
}
|
|
@@ -544,6 +544,10 @@ export class TileList extends BaseTileComponent {
|
|
|
544
544
|
overflow-wrap: anywhere;
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
+
#collections > a {
|
|
548
|
+
display: inline-block;
|
|
549
|
+
}
|
|
550
|
+
|
|
547
551
|
#icon {
|
|
548
552
|
padding-top: 5px;
|
|
549
553
|
}
|
|
@@ -20,6 +20,7 @@ import { analyticsCategories } from '../src/utils/analytics-events';
|
|
|
20
20
|
import type { TileDispatcher } from '../src/tiles/tile-dispatcher';
|
|
21
21
|
import type { CollectionFacets } from '../src/collection-facets';
|
|
22
22
|
import type { EmptyPlaceholder } from '../src/empty-placeholder';
|
|
23
|
+
import type { SortFilterBar } from '../src/sort-filter-bar/sort-filter-bar';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Wait for the next tick of the event loop.
|
|
@@ -93,7 +94,7 @@ describe('Collection Browser', () => {
|
|
|
93
94
|
el.clearFilters({ sort: true }); // Sort is reset too due to the option
|
|
94
95
|
|
|
95
96
|
expect(el.selectedFacets).to.deep.equal(getDefaultSelectedFacets());
|
|
96
|
-
expect(el.selectedSort).to.equal(
|
|
97
|
+
expect(el.selectedSort).to.equal(SortField.default);
|
|
97
98
|
expect(el.sortDirection).to.be.null;
|
|
98
99
|
expect(el.sortParam).to.be.null;
|
|
99
100
|
expect(el.selectedCreatorFilter).to.be.null;
|
|
@@ -763,7 +764,7 @@ describe('Collection Browser', () => {
|
|
|
763
764
|
</collection-browser>`
|
|
764
765
|
);
|
|
765
766
|
|
|
766
|
-
expect(el.selectedSort).to.equal(SortField.
|
|
767
|
+
expect(el.selectedSort).to.equal(SortField.default);
|
|
767
768
|
|
|
768
769
|
el.baseQuery = 'foo';
|
|
769
770
|
await el.updateComplete;
|
|
@@ -988,6 +989,82 @@ describe('Collection Browser', () => {
|
|
|
988
989
|
).to.equal('/details/foo?q=%22quoted+query%22');
|
|
989
990
|
});
|
|
990
991
|
|
|
992
|
+
it('sets default sort from collection metadata', async () => {
|
|
993
|
+
const searchService = new MockSearchService();
|
|
994
|
+
const el = await fixture<CollectionBrowser>(
|
|
995
|
+
html`<collection-browser
|
|
996
|
+
.searchService=${searchService}
|
|
997
|
+
.baseNavigationUrl=${''}
|
|
998
|
+
></collection-browser>`
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
el.withinCollection = 'default-sort';
|
|
1002
|
+
await el.updateComplete;
|
|
1003
|
+
await el.initialSearchComplete;
|
|
1004
|
+
await el.updateComplete;
|
|
1005
|
+
await aTimeout(50);
|
|
1006
|
+
|
|
1007
|
+
const sortBar = el.shadowRoot?.querySelector(
|
|
1008
|
+
'sort-filter-bar'
|
|
1009
|
+
) as SortFilterBar;
|
|
1010
|
+
expect(sortBar).to.exist;
|
|
1011
|
+
expect(sortBar.defaultSortField).to.equal(SortField.title);
|
|
1012
|
+
expect(sortBar.defaultSortDirection).to.equal('asc');
|
|
1013
|
+
expect(sortBar.selectedSort).to.equal(SortField.default);
|
|
1014
|
+
expect(sortBar.sortDirection).to.be.null;
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
it('sets default sort from collection metadata in "-field" format', async () => {
|
|
1018
|
+
const searchService = new MockSearchService();
|
|
1019
|
+
const el = await fixture<CollectionBrowser>(
|
|
1020
|
+
html`<collection-browser
|
|
1021
|
+
.searchService=${searchService}
|
|
1022
|
+
.baseNavigationUrl=${''}
|
|
1023
|
+
></collection-browser>`
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
el.withinCollection = 'default-sort-concise';
|
|
1027
|
+
await el.updateComplete;
|
|
1028
|
+
await el.initialSearchComplete;
|
|
1029
|
+
await el.updateComplete;
|
|
1030
|
+
await aTimeout(50);
|
|
1031
|
+
|
|
1032
|
+
const sortBar = el.shadowRoot?.querySelector(
|
|
1033
|
+
'sort-filter-bar'
|
|
1034
|
+
) as SortFilterBar;
|
|
1035
|
+
expect(sortBar).to.exist;
|
|
1036
|
+
expect(sortBar.defaultSortField).to.equal(SortField.dateadded);
|
|
1037
|
+
expect(sortBar.defaultSortDirection).to.equal('desc');
|
|
1038
|
+
expect(sortBar.selectedSort).to.equal(SortField.default);
|
|
1039
|
+
expect(sortBar.sortDirection).to.be.null;
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it('uses relevance sort as default when a query is set', async () => {
|
|
1043
|
+
const searchService = new MockSearchService();
|
|
1044
|
+
const el = await fixture<CollectionBrowser>(
|
|
1045
|
+
html`<collection-browser
|
|
1046
|
+
.searchService=${searchService}
|
|
1047
|
+
.baseNavigationUrl=${''}
|
|
1048
|
+
></collection-browser>`
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
el.withinCollection = 'default-sort';
|
|
1052
|
+
el.baseQuery = 'default-sort';
|
|
1053
|
+
await el.updateComplete;
|
|
1054
|
+
await el.initialSearchComplete;
|
|
1055
|
+
await el.updateComplete;
|
|
1056
|
+
await aTimeout(50);
|
|
1057
|
+
|
|
1058
|
+
const sortBar = el.shadowRoot?.querySelector(
|
|
1059
|
+
'sort-filter-bar'
|
|
1060
|
+
) as SortFilterBar;
|
|
1061
|
+
expect(sortBar).to.exist;
|
|
1062
|
+
expect(sortBar.defaultSortField).to.equal(SortField.relevance);
|
|
1063
|
+
expect(sortBar.defaultSortDirection).to.be.null;
|
|
1064
|
+
expect(sortBar.selectedSort).to.equal(SortField.default);
|
|
1065
|
+
expect(sortBar.sortDirection).to.be.null;
|
|
1066
|
+
});
|
|
1067
|
+
|
|
991
1068
|
it('scrolls to page', async () => {
|
|
992
1069
|
const searchService = new MockSearchService();
|
|
993
1070
|
const el = await fixture<CollectionBrowser>(
|