@internetarchive/collection-browser 1.8.0 → 1.9.0-alpha1

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.
Files changed (269) hide show
  1. package/.editorconfig +29 -29
  2. package/.github/workflows/ci.yml +26 -26
  3. package/.github/workflows/gh-pages-main.yml +39 -39
  4. package/.github/workflows/npm-publish.yml +39 -39
  5. package/.github/workflows/pr-preview.yml +38 -38
  6. package/.husky/pre-commit +4 -4
  7. package/LICENSE +661 -661
  8. package/README.md +83 -83
  9. package/dist/index.d.ts +9 -9
  10. package/dist/index.js +9 -9
  11. package/dist/src/app-root.d.ts +54 -54
  12. package/dist/src/app-root.js +293 -293
  13. package/dist/src/assets/img/icons/arrow-left.d.ts +2 -2
  14. package/dist/src/assets/img/icons/arrow-left.js +2 -2
  15. package/dist/src/assets/img/icons/arrow-right.d.ts +2 -2
  16. package/dist/src/assets/img/icons/arrow-right.js +2 -2
  17. package/dist/src/assets/img/icons/chevron.d.ts +2 -2
  18. package/dist/src/assets/img/icons/chevron.js +2 -2
  19. package/dist/src/assets/img/icons/contract.d.ts +2 -2
  20. package/dist/src/assets/img/icons/contract.js +2 -2
  21. package/dist/src/assets/img/icons/empty-query.d.ts +2 -2
  22. package/dist/src/assets/img/icons/empty-query.js +2 -2
  23. package/dist/src/assets/img/icons/expand.d.ts +2 -2
  24. package/dist/src/assets/img/icons/expand.js +2 -2
  25. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -2
  26. package/dist/src/assets/img/icons/eye-closed.js +2 -2
  27. package/dist/src/assets/img/icons/eye.d.ts +2 -2
  28. package/dist/src/assets/img/icons/eye.js +2 -2
  29. package/dist/src/assets/img/icons/favorite-filled.d.ts +1 -1
  30. package/dist/src/assets/img/icons/favorite-filled.js +2 -2
  31. package/dist/src/assets/img/icons/login-required.d.ts +1 -1
  32. package/dist/src/assets/img/icons/login-required.js +2 -2
  33. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -1
  34. package/dist/src/assets/img/icons/mediatype/account.js +2 -2
  35. package/dist/src/assets/img/icons/mediatype/audio.d.ts +1 -1
  36. package/dist/src/assets/img/icons/mediatype/audio.js +2 -2
  37. package/dist/src/assets/img/icons/mediatype/collection.d.ts +1 -1
  38. package/dist/src/assets/img/icons/mediatype/collection.js +2 -2
  39. package/dist/src/assets/img/icons/mediatype/data.d.ts +1 -1
  40. package/dist/src/assets/img/icons/mediatype/data.js +2 -2
  41. package/dist/src/assets/img/icons/mediatype/etree.d.ts +1 -1
  42. package/dist/src/assets/img/icons/mediatype/etree.js +2 -2
  43. package/dist/src/assets/img/icons/mediatype/film.d.ts +1 -1
  44. package/dist/src/assets/img/icons/mediatype/film.js +2 -2
  45. package/dist/src/assets/img/icons/mediatype/images.d.ts +1 -1
  46. package/dist/src/assets/img/icons/mediatype/images.js +2 -2
  47. package/dist/src/assets/img/icons/mediatype/radio.d.ts +1 -1
  48. package/dist/src/assets/img/icons/mediatype/radio.js +2 -2
  49. package/dist/src/assets/img/icons/mediatype/software.d.ts +1 -1
  50. package/dist/src/assets/img/icons/mediatype/software.js +2 -2
  51. package/dist/src/assets/img/icons/mediatype/texts.d.ts +1 -1
  52. package/dist/src/assets/img/icons/mediatype/texts.js +2 -2
  53. package/dist/src/assets/img/icons/mediatype/tv.d.ts +1 -1
  54. package/dist/src/assets/img/icons/mediatype/tv.js +2 -2
  55. package/dist/src/assets/img/icons/mediatype/video.d.ts +1 -1
  56. package/dist/src/assets/img/icons/mediatype/video.js +2 -2
  57. package/dist/src/assets/img/icons/mediatype/web.d.ts +1 -1
  58. package/dist/src/assets/img/icons/mediatype/web.js +2 -2
  59. package/dist/src/assets/img/icons/null-result.d.ts +2 -2
  60. package/dist/src/assets/img/icons/null-result.js +2 -2
  61. package/dist/src/assets/img/icons/restricted.d.ts +1 -1
  62. package/dist/src/assets/img/icons/restricted.js +2 -2
  63. package/dist/src/assets/img/icons/reviews.d.ts +1 -1
  64. package/dist/src/assets/img/icons/reviews.js +2 -2
  65. package/dist/src/assets/img/icons/upload.d.ts +1 -1
  66. package/dist/src/assets/img/icons/upload.js +2 -2
  67. package/dist/src/assets/img/icons/views.d.ts +1 -1
  68. package/dist/src/assets/img/icons/views.js +2 -2
  69. package/dist/src/circular-activity-indicator.d.ts +5 -5
  70. package/dist/src/circular-activity-indicator.js +17 -17
  71. package/dist/src/collection-browser.d.ts +469 -469
  72. package/dist/src/collection-browser.js +1666 -1653
  73. package/dist/src/collection-browser.js.map +1 -1
  74. package/dist/src/collection-facets/facet-tombstone-row.d.ts +5 -5
  75. package/dist/src/collection-facets/facet-tombstone-row.js +15 -15
  76. package/dist/src/collection-facets/facets-template.d.ts +20 -20
  77. package/dist/src/collection-facets/facets-template.js +152 -152
  78. package/dist/src/collection-facets/more-facets-content.d.ts +77 -77
  79. package/dist/src/collection-facets/more-facets-content.js +359 -359
  80. package/dist/src/collection-facets/more-facets-pagination.d.ts +36 -36
  81. package/dist/src/collection-facets/more-facets-pagination.js +196 -196
  82. package/dist/src/collection-facets/toggle-switch.d.ts +41 -41
  83. package/dist/src/collection-facets/toggle-switch.js +94 -94
  84. package/dist/src/collection-facets.d.ts +99 -99
  85. package/dist/src/collection-facets.js +471 -471
  86. package/dist/src/empty-placeholder.d.ts +21 -21
  87. package/dist/src/empty-placeholder.js +69 -69
  88. package/dist/src/expanded-date-picker.d.ts +43 -43
  89. package/dist/src/expanded-date-picker.js +109 -109
  90. package/dist/src/language-code-handler/language-code-handler.d.ts +37 -37
  91. package/dist/src/language-code-handler/language-code-handler.js +26 -26
  92. package/dist/src/language-code-handler/language-code-mapping.d.ts +1 -1
  93. package/dist/src/language-code-handler/language-code-mapping.js +562 -562
  94. package/dist/src/mediatype/mediatype-config.d.ts +3 -3
  95. package/dist/src/mediatype/mediatype-config.js +85 -85
  96. package/dist/src/models.d.ts +162 -149
  97. package/dist/src/models.js +256 -195
  98. package/dist/src/models.js.map +1 -1
  99. package/dist/src/restoration-state-handler.d.ts +70 -63
  100. package/dist/src/restoration-state-handler.js +355 -326
  101. package/dist/src/restoration-state-handler.js.map +1 -1
  102. package/dist/src/sort-filter-bar/alpha-bar-tooltip.d.ts +6 -6
  103. package/dist/src/sort-filter-bar/alpha-bar-tooltip.js +24 -24
  104. package/dist/src/sort-filter-bar/alpha-bar.d.ts +21 -21
  105. package/dist/src/sort-filter-bar/alpha-bar.js +128 -128
  106. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -1
  107. package/dist/src/sort-filter-bar/img/compact.js +2 -2
  108. package/dist/src/sort-filter-bar/img/list.d.ts +1 -1
  109. package/dist/src/sort-filter-bar/img/list.js +2 -2
  110. package/dist/src/sort-filter-bar/img/sort-toggle-disabled.d.ts +1 -1
  111. package/dist/src/sort-filter-bar/img/sort-toggle-disabled.js +2 -2
  112. package/dist/src/sort-filter-bar/img/sort-toggle-down.d.ts +1 -1
  113. package/dist/src/sort-filter-bar/img/sort-toggle-down.js +2 -2
  114. package/dist/src/sort-filter-bar/img/sort-toggle-up.d.ts +1 -1
  115. package/dist/src/sort-filter-bar/img/sort-toggle-up.js +2 -2
  116. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -1
  117. package/dist/src/sort-filter-bar/img/sort-triangle.js +2 -2
  118. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -1
  119. package/dist/src/sort-filter-bar/img/tile.js +2 -2
  120. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +201 -199
  121. package/dist/src/sort-filter-bar/sort-filter-bar.js +622 -617
  122. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  123. package/dist/src/styles/item-image-styles.d.ts +8 -8
  124. package/dist/src/styles/item-image-styles.js +9 -9
  125. package/dist/src/styles/sr-only.d.ts +1 -1
  126. package/dist/src/styles/sr-only.js +2 -2
  127. package/dist/src/tiles/base-tile-component.d.ts +18 -18
  128. package/dist/src/tiles/base-tile-component.js +59 -59
  129. package/dist/src/tiles/collection-browser-loading-tile.d.ts +5 -5
  130. package/dist/src/tiles/collection-browser-loading-tile.js +15 -15
  131. package/dist/src/tiles/grid/account-tile.d.ts +18 -18
  132. package/dist/src/tiles/grid/account-tile.js +72 -72
  133. package/dist/src/tiles/grid/collection-tile.d.ts +15 -15
  134. package/dist/src/tiles/grid/collection-tile.js +80 -80
  135. package/dist/src/tiles/grid/item-tile.d.ts +27 -27
  136. package/dist/src/tiles/grid/item-tile.js +134 -134
  137. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.d.ts +1 -1
  138. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.js +8 -8
  139. package/dist/src/tiles/grid/tile-stats.d.ts +11 -11
  140. package/dist/src/tiles/grid/tile-stats.js +48 -48
  141. package/dist/src/tiles/hover/hover-pane-controller.d.ts +219 -219
  142. package/dist/src/tiles/hover/hover-pane-controller.js +352 -352
  143. package/dist/src/tiles/hover/tile-hover-pane.d.ts +15 -15
  144. package/dist/src/tiles/hover/tile-hover-pane.js +38 -38
  145. package/dist/src/tiles/image-block.d.ts +17 -17
  146. package/dist/src/tiles/image-block.js +72 -72
  147. package/dist/src/tiles/item-image.d.ts +35 -35
  148. package/dist/src/tiles/item-image.js +117 -117
  149. package/dist/src/tiles/list/tile-list-compact-header.d.ts +6 -6
  150. package/dist/src/tiles/list/tile-list-compact-header.js +38 -38
  151. package/dist/src/tiles/list/tile-list-compact.d.ts +15 -15
  152. package/dist/src/tiles/list/tile-list-compact.js +114 -114
  153. package/dist/src/tiles/list/tile-list.d.ts +46 -46
  154. package/dist/src/tiles/list/tile-list.js +298 -298
  155. package/dist/src/tiles/mediatype-icon.d.ts +9 -9
  156. package/dist/src/tiles/mediatype-icon.js +47 -47
  157. package/dist/src/tiles/overlay/icon-overlay.d.ts +10 -10
  158. package/dist/src/tiles/overlay/icon-overlay.js +40 -40
  159. package/dist/src/tiles/overlay/icon-text-overlay.d.ts +9 -9
  160. package/dist/src/tiles/overlay/icon-text-overlay.js +38 -38
  161. package/dist/src/tiles/overlay/text-overlay.d.ts +10 -10
  162. package/dist/src/tiles/overlay/text-overlay.js +42 -42
  163. package/dist/src/tiles/text-snippet-block.d.ts +27 -27
  164. package/dist/src/tiles/text-snippet-block.js +73 -73
  165. package/dist/src/tiles/tile-dispatcher.d.ts +50 -50
  166. package/dist/src/tiles/tile-dispatcher.js +185 -185
  167. package/dist/src/tiles/tile-display-value-provider.d.ts +43 -43
  168. package/dist/src/tiles/tile-display-value-provider.js +80 -80
  169. package/dist/src/utils/analytics-events.d.ts +24 -24
  170. package/dist/src/utils/analytics-events.js +26 -26
  171. package/dist/src/utils/array-equals.d.ts +4 -4
  172. package/dist/src/utils/array-equals.js +10 -10
  173. package/dist/src/utils/format-count.d.ts +7 -7
  174. package/dist/src/utils/format-count.js +76 -76
  175. package/dist/src/utils/format-date.d.ts +2 -2
  176. package/dist/src/utils/format-date.js +25 -25
  177. package/dist/src/utils/format-unit-size.d.ts +2 -2
  178. package/dist/src/utils/format-unit-size.js +33 -33
  179. package/dist/src/utils/local-date-from-utc.d.ts +9 -9
  180. package/dist/src/utils/local-date-from-utc.js +15 -15
  181. package/dist/src/utils/sha1.d.ts +2 -2
  182. package/dist/src/utils/sha1.js +8 -8
  183. package/dist/test/collection-browser.test.d.ts +1 -1
  184. package/dist/test/collection-browser.test.js +808 -808
  185. package/dist/test/collection-facets/facets-template.test.d.ts +1 -1
  186. package/dist/test/collection-facets/facets-template.test.js +134 -134
  187. package/dist/test/collection-facets/more-facets-content.test.d.ts +1 -1
  188. package/dist/test/collection-facets/more-facets-content.test.js +133 -133
  189. package/dist/test/collection-facets/more-facets-pagination.test.d.ts +1 -1
  190. package/dist/test/collection-facets/more-facets-pagination.test.js +117 -117
  191. package/dist/test/collection-facets/toggle-switch.test.d.ts +1 -1
  192. package/dist/test/collection-facets/toggle-switch.test.js +73 -73
  193. package/dist/test/collection-facets.test.d.ts +2 -2
  194. package/dist/test/collection-facets.test.js +645 -645
  195. package/dist/test/empty-placeholder.test.d.ts +1 -1
  196. package/dist/test/empty-placeholder.test.js +56 -56
  197. package/dist/test/expanded-date-picker.test.d.ts +1 -1
  198. package/dist/test/expanded-date-picker.test.js +95 -95
  199. package/dist/test/icon-overlay.test.d.ts +1 -1
  200. package/dist/test/icon-overlay.test.js +24 -24
  201. package/dist/test/image-block.test.d.ts +1 -1
  202. package/dist/test/image-block.test.js +48 -48
  203. package/dist/test/item-image.test.d.ts +1 -1
  204. package/dist/test/item-image.test.js +84 -84
  205. package/dist/test/mediatype-config.test.d.ts +1 -1
  206. package/dist/test/mediatype-config.test.js +16 -16
  207. package/dist/test/mocks/mock-analytics-handler.d.ts +10 -10
  208. package/dist/test/mocks/mock-analytics-handler.js +15 -15
  209. package/dist/test/mocks/mock-collection-name-cache.d.ts +9 -9
  210. package/dist/test/mocks/mock-collection-name-cache.js +17 -17
  211. package/dist/test/mocks/mock-search-responses.d.ts +19 -19
  212. package/dist/test/mocks/mock-search-responses.js +623 -623
  213. package/dist/test/mocks/mock-search-service.d.ts +15 -15
  214. package/dist/test/mocks/mock-search-service.js +48 -48
  215. package/dist/test/restoration-state-handler.test.d.ts +1 -1
  216. package/dist/test/restoration-state-handler.test.js +270 -218
  217. package/dist/test/restoration-state-handler.test.js.map +1 -1
  218. package/dist/test/sort-filter-bar/alpha-bar-tooltip.test.d.ts +1 -1
  219. package/dist/test/sort-filter-bar/alpha-bar-tooltip.test.js +12 -12
  220. package/dist/test/sort-filter-bar/alpha-bar.test.d.ts +1 -1
  221. package/dist/test/sort-filter-bar/alpha-bar.test.js +73 -73
  222. package/dist/test/sort-filter-bar/sort-filter-bar.test.d.ts +1 -1
  223. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +378 -378
  224. package/dist/test/text-overlay.test.d.ts +1 -1
  225. package/dist/test/text-overlay.test.js +48 -48
  226. package/dist/test/text-snippet-block.test.d.ts +1 -1
  227. package/dist/test/text-snippet-block.test.js +57 -57
  228. package/dist/test/tile-stats.test.d.ts +1 -1
  229. package/dist/test/tile-stats.test.js +33 -33
  230. package/dist/test/tiles/grid/account-tile.test.d.ts +1 -1
  231. package/dist/test/tiles/grid/account-tile.test.js +76 -76
  232. package/dist/test/tiles/grid/collection-tile.test.d.ts +1 -1
  233. package/dist/test/tiles/grid/collection-tile.test.js +73 -73
  234. package/dist/test/tiles/grid/item-tile.test.d.ts +1 -1
  235. package/dist/test/tiles/grid/item-tile.test.js +254 -254
  236. package/dist/test/tiles/hover/hover-pane-controller.test.d.ts +1 -1
  237. package/dist/test/tiles/hover/hover-pane-controller.test.js +257 -257
  238. package/dist/test/tiles/hover/tile-hover-pane.test.d.ts +1 -1
  239. package/dist/test/tiles/hover/tile-hover-pane.test.js +13 -13
  240. package/dist/test/tiles/list/tile-list-compact.test.d.ts +1 -1
  241. package/dist/test/tiles/list/tile-list-compact.test.js +143 -143
  242. package/dist/test/tiles/list/tile-list.test.d.ts +1 -1
  243. package/dist/test/tiles/list/tile-list.test.js +242 -242
  244. package/dist/test/tiles/tile-dispatcher.test.d.ts +1 -1
  245. package/dist/test/tiles/tile-dispatcher.test.js +67 -67
  246. package/dist/test/tiles/tile-display-value-provider.test.d.ts +1 -1
  247. package/dist/test/tiles/tile-display-value-provider.test.js +141 -141
  248. package/dist/test/utils/array-equals.test.d.ts +1 -1
  249. package/dist/test/utils/array-equals.test.js +26 -26
  250. package/dist/test/utils/format-count.test.d.ts +1 -1
  251. package/dist/test/utils/format-count.test.js +23 -23
  252. package/dist/test/utils/format-date.test.d.ts +1 -1
  253. package/dist/test/utils/format-date.test.js +17 -17
  254. package/dist/test/utils/format-unit-size.test.d.ts +1 -1
  255. package/dist/test/utils/format-unit-size.test.js +17 -17
  256. package/dist/test/utils/local-date-from-utc.test.d.ts +1 -1
  257. package/dist/test/utils/local-date-from-utc.test.js +26 -26
  258. package/local.archive.org.cert +86 -86
  259. package/local.archive.org.key +27 -27
  260. package/package.json +1 -1
  261. package/renovate.json +6 -6
  262. package/src/collection-browser.ts +24 -8
  263. package/src/models.ts +193 -109
  264. package/src/restoration-state-handler.ts +66 -40
  265. package/src/sort-filter-bar/sort-filter-bar.ts +34 -27
  266. package/test/restoration-state-handler.test.ts +68 -1
  267. package/tsconfig.json +21 -21
  268. package/web-dev-server.config.mjs +30 -30
  269. package/web-test-runner.config.mjs +41 -41
package/src/models.ts CHANGED
@@ -56,6 +56,7 @@ export type CollectionBrowserContext = 'collection' | 'search';
56
56
  */
57
57
  export enum SortField {
58
58
  'default' = 'default',
59
+ 'unrecognized' = 'unrecognized',
59
60
  'relevance' = 'relevance',
60
61
  'alltimeview' = 'alltimeview',
61
62
  'weeklyview' = 'weeklyview',
@@ -67,120 +68,203 @@ export enum SortField {
67
68
  'creator' = 'creator',
68
69
  }
69
70
 
70
- /**
71
- * The metadata fields we sort by that map to the SortFields above
72
- */
73
- export type MetadataSortField =
74
- | 'downloads'
75
- | 'week'
76
- | 'titleSorter'
77
- | 'date'
78
- | 'creatorSorter'
79
- | 'publicdate'
80
- | 'reviewdate'
81
- | 'addeddate';
82
-
83
- /**
84
- * All sort strings recognized in search URLs
85
- */
86
- export type URLSortField = MetadataSortField | 'title' | 'creator';
87
-
88
- export const SortFieldDisplayName: {
89
- [key in SortField]: string;
90
- } = {
91
- default: '', // Use the default sorting option for the current page context, if none has been selected
92
- relevance: 'Relevance',
93
- alltimeview: 'All-time views',
94
- weeklyview: 'Weekly views',
95
- title: 'Title',
96
- date: 'Date published',
97
- datearchived: 'Date archived',
98
- datereviewed: 'Date reviewed',
99
- dateadded: 'Date added',
100
- creator: 'Creator',
101
- };
102
-
103
- export const DefaultSortDirection: {
104
- [key in SortField]: SortDirection | null;
105
- } = {
106
- default: null,
107
- relevance: null, // Sort direction is disabled entirely for relevance sort (user can't click the button)
108
- alltimeview: 'desc',
109
- weeklyview: 'desc',
110
- title: 'asc',
111
- date: 'desc',
112
- datearchived: 'desc',
113
- datereviewed: 'desc',
114
- dateadded: 'desc',
115
- creator: 'asc',
116
- };
117
-
118
- /**
119
- * Maps the SortField above to the corresponding Metadata field in the API.
120
- */
121
- export const SortFieldToMetadataField: {
122
- [key in SortField]: MetadataSortField | null;
123
- } = {
124
- default: null,
125
- relevance: null,
126
- alltimeview: 'downloads',
127
- weeklyview: 'week',
128
- title: 'titleSorter',
129
- date: 'date',
130
- datearchived: 'publicdate',
131
- datereviewed: 'reviewdate',
132
- dateadded: 'addeddate',
133
- creator: 'creatorSorter',
134
- };
135
-
136
- /**
137
- * Maps the Metadata field to the corresponding SortField field in the API.
138
- */
139
- export const MetadataFieldToSortField: {
140
- [key in MetadataSortField]: SortField;
141
- } = {
142
- week: SortField.weeklyview,
143
- downloads: SortField.alltimeview,
144
- titleSorter: SortField.title,
145
- date: SortField.date,
146
- publicdate: SortField.datearchived,
147
- reviewdate: SortField.datereviewed,
148
- addeddate: SortField.dateadded,
149
- creatorSorter: SortField.creator,
150
- };
71
+ export interface SortOption {
72
+ /**
73
+ * The SortField enum member corresponding to this option.
74
+ */
75
+ field: SortField;
76
+
77
+ /**
78
+ * The default sort direction to apply when this sort option is first selected.
79
+ */
80
+ defaultSortDirection: SortDirection | null;
81
+
82
+ /**
83
+ * Whether this sort option allows its sort direction to be changed from the default.
84
+ */
85
+ canSetDirection: boolean;
86
+
87
+ /**
88
+ * Whether this sort option may appear in the sort bar.
89
+ */
90
+ shownInSortBar: boolean;
91
+
92
+ /**
93
+ * Whether this sort option should be saved to the URL.
94
+ * If false, then no `sort` param will be added to the URL when this sort option
95
+ * is selected.
96
+ */
97
+ shownInURL: boolean;
98
+
99
+ /**
100
+ * Whether this sort option is passed to the search service.
101
+ * If false, then no sort param will be passed to the search service at all when
102
+ * this sort option is selected.
103
+ */
104
+ handledBySearchService: boolean;
105
+
106
+ /**
107
+ * The string identifying this sort field to the search service & backend API.
108
+ */
109
+ searchServiceKey?: string;
110
+
111
+ /**
112
+ * The human-readable name to use for this option in the sort bar (if applicable).
113
+ */
114
+ displayName: string;
115
+
116
+ /**
117
+ * A list of URL param keys that should be mapped to this sort option.
118
+ * E.g., both `title` and `titleSorter` in the URL map to the `SortField.title` option.
119
+ */
120
+ urlNames: (string | null | undefined)[];
121
+ }
151
122
 
152
- /**
153
- * Maps the Metadata fields to how they should be presented in URLs.
154
- */
155
- export const MetadataFieldToURLField: {
156
- [key in MetadataSortField]: URLSortField;
157
- } = {
158
- week: 'week',
159
- downloads: 'downloads',
160
- titleSorter: 'title',
161
- date: 'date',
162
- publicdate: 'publicdate',
163
- reviewdate: 'reviewdate',
164
- addeddate: 'addeddate',
165
- creatorSorter: 'creator',
123
+ export const SORT_OPTIONS: Record<SortField, SortOption> = {
124
+ // Default sort is the case where the user has not specified a sort option via the sort bar or URL.
125
+ // In these cases, we defer to whatever sort the backend chooses.
126
+ // For the search page, the default is always relevance sort.
127
+ // For collection pages _without a query_, the default is usually weekly views, but this can be
128
+ // overridden by the collection's `sort-by` metadata entry. If a query _is_ specified, then the
129
+ // default is again relevance sort.
130
+ [SortField.default]: {
131
+ field: SortField.default,
132
+ defaultSortDirection: null,
133
+ canSetDirection: false,
134
+ shownInSortBar: false,
135
+ shownInURL: false,
136
+ handledBySearchService: false, // We rely on the PPS default sort handling in these cases
137
+ displayName: '',
138
+ urlNames: ['', null, undefined], // Empty or nullish sort params result in default sorting
139
+ },
140
+ // Unrecognized sort is the case where the user has specified a sort in the URL, but it is not
141
+ // one of the options listed in this map. We still want these unrecognized sorts to be applied
142
+ // when searching, but they are not displayed in the sort bar and we do not actively manage
143
+ // their URL param beyond flipping the direction as needed.
144
+ [SortField.unrecognized]: {
145
+ field: SortField.unrecognized,
146
+ defaultSortDirection: null,
147
+ canSetDirection: true,
148
+ shownInSortBar: false,
149
+ shownInURL: false,
150
+ handledBySearchService: true, // The unrecognized sort param is passed along as-is
151
+ displayName: '',
152
+ urlNames: [],
153
+ },
154
+ // Relevance sort is unique in that it does not produce a URL param when it is set.
155
+ // It is only available when there is a user-specified query that relevancy can be scored against.
156
+ // Therefore, it does not appear as a sort bar option when browsing a collection with no query set.
157
+ [SortField.relevance]: {
158
+ field: SortField.relevance,
159
+ defaultSortDirection: null,
160
+ canSetDirection: false,
161
+ shownInSortBar: true,
162
+ shownInURL: false,
163
+ handledBySearchService: false,
164
+ displayName: 'Relevance',
165
+ urlNames: ['_score'],
166
+ },
167
+ [SortField.alltimeview]: {
168
+ field: SortField.alltimeview,
169
+ defaultSortDirection: 'desc',
170
+ canSetDirection: true,
171
+ shownInSortBar: true,
172
+ shownInURL: true,
173
+ handledBySearchService: true,
174
+ searchServiceKey: 'downloads',
175
+ displayName: 'All-time views',
176
+ urlNames: ['downloads'],
177
+ },
178
+ [SortField.weeklyview]: {
179
+ field: SortField.weeklyview,
180
+ defaultSortDirection: 'desc',
181
+ canSetDirection: true,
182
+ shownInSortBar: true,
183
+ shownInURL: true,
184
+ handledBySearchService: true,
185
+ searchServiceKey: 'week',
186
+ displayName: 'Weekly views',
187
+ urlNames: ['week'],
188
+ },
189
+ [SortField.title]: {
190
+ field: SortField.title,
191
+ defaultSortDirection: 'asc',
192
+ canSetDirection: true,
193
+ shownInSortBar: true,
194
+ shownInURL: true,
195
+ handledBySearchService: true,
196
+ searchServiceKey: 'titleSorter',
197
+ displayName: 'Title',
198
+ urlNames: ['title', 'titleSorter'],
199
+ },
200
+ [SortField.date]: {
201
+ field: SortField.date,
202
+ defaultSortDirection: 'desc',
203
+ canSetDirection: true,
204
+ shownInSortBar: true,
205
+ shownInURL: true,
206
+ handledBySearchService: true,
207
+ searchServiceKey: 'date',
208
+ displayName: 'Date published',
209
+ urlNames: ['date'],
210
+ },
211
+ [SortField.datearchived]: {
212
+ field: SortField.datearchived,
213
+ defaultSortDirection: 'desc',
214
+ canSetDirection: true,
215
+ shownInSortBar: true,
216
+ shownInURL: true,
217
+ handledBySearchService: true,
218
+ searchServiceKey: 'publicdate',
219
+ displayName: 'Date archived',
220
+ urlNames: ['publicdate'],
221
+ },
222
+ [SortField.datereviewed]: {
223
+ field: SortField.datereviewed,
224
+ defaultSortDirection: 'desc',
225
+ canSetDirection: true,
226
+ shownInSortBar: true,
227
+ shownInURL: true,
228
+ handledBySearchService: true,
229
+ searchServiceKey: 'reviewdate',
230
+ displayName: 'Date reviewed',
231
+ urlNames: ['reviewdate'],
232
+ },
233
+ [SortField.dateadded]: {
234
+ field: SortField.dateadded,
235
+ defaultSortDirection: 'desc',
236
+ canSetDirection: true,
237
+ shownInSortBar: true,
238
+ shownInURL: true,
239
+ handledBySearchService: true,
240
+ searchServiceKey: 'addeddate',
241
+ displayName: 'Date added',
242
+ urlNames: ['addeddate'],
243
+ },
244
+ [SortField.creator]: {
245
+ field: SortField.creator,
246
+ defaultSortDirection: 'asc',
247
+ canSetDirection: true,
248
+ shownInSortBar: true,
249
+ shownInURL: true,
250
+ handledBySearchService: true,
251
+ searchServiceKey: 'creatorSorter',
252
+ displayName: 'Creator',
253
+ urlNames: ['creator', 'creatorSorter'],
254
+ },
166
255
  };
167
256
 
168
257
  /**
169
- * Maps all allowable sort strings from the URL to their respective
170
- * sort fields.
258
+ * Returns the SortOption corresponding to the given API sort name, or
259
+ * the "unrecognized" SortOption if none matches.
171
260
  */
172
- export const URLFieldToSortField: Record<URLSortField, SortField> = {
173
- week: SortField.weeklyview,
174
- downloads: SortField.alltimeview,
175
- title: SortField.title,
176
- titleSorter: SortField.title,
177
- date: SortField.date,
178
- publicdate: SortField.datearchived,
179
- reviewdate: SortField.datereviewed,
180
- addeddate: SortField.dateadded,
181
- creator: SortField.creator,
182
- creatorSorter: SortField.creator,
183
- };
261
+ export function sortOptionFromAPIString(sortName?: string | null): SortOption {
262
+ return (
263
+ Object.values(SORT_OPTIONS).find(opt =>
264
+ opt.urlNames.some(name => sortName === name)
265
+ ) ?? SORT_OPTIONS[SortField.unrecognized]
266
+ );
267
+ }
184
268
 
185
269
  /** A union of the fields that permit prefix filtering (e.g., alphabetical filtering) */
186
270
  export type PrefixFilterType = 'title' | 'creator';
@@ -1,11 +1,6 @@
1
- import {
2
- SearchType,
3
- SortDirection,
4
- SortParam,
5
- } from '@internetarchive/search-service';
1
+ import { SearchType, SortDirection } from '@internetarchive/search-service';
6
2
  import { getCookie, setCookie } from 'typescript-cookie';
7
3
  import {
8
- MetadataSortField,
9
4
  FacetOption,
10
5
  CollectionBrowserContext,
11
6
  CollectionDisplayMode,
@@ -13,17 +8,15 @@ import {
13
8
  SortField,
14
9
  FacetBucket,
15
10
  FacetState,
16
- URLFieldToSortField,
17
- URLSortField,
18
11
  getDefaultSelectedFacets,
19
- MetadataFieldToURLField,
12
+ sortOptionFromAPIString,
13
+ SORT_OPTIONS,
20
14
  } from './models';
21
15
  import { arrayEquals } from './utils/array-equals';
22
16
 
23
17
  export interface RestorationState {
24
18
  displayMode?: CollectionDisplayMode;
25
19
  searchType?: SearchType;
26
- sortParam?: SortParam;
27
20
  selectedSort?: SortField;
28
21
  sortDirection?: SortDirection;
29
22
  selectedFacets: SelectedFacets;
@@ -120,11 +113,29 @@ export class RestorationStateHandler
120
113
  }
121
114
  }
122
115
 
123
- if (state.sortParam) {
124
- const prefix = state.sortParam.direction === 'desc' ? '-' : '';
125
- const readableSortField =
126
- MetadataFieldToURLField[state.sortParam.field as MetadataSortField];
127
- newParams.set('sort', `${prefix}${readableSortField}`);
116
+ if (state.selectedSort) {
117
+ const sortOption = SORT_OPTIONS[state.selectedSort];
118
+ let prefix = this.sortDirectionPrefix(state.sortDirection);
119
+
120
+ if (sortOption.field === SortField.unrecognized) {
121
+ // For unrecognized sorts, use the existing param, possibly updating its direction
122
+ const oldSortParam = oldParams.get('sort') ?? '';
123
+ const { field, direction } =
124
+ this.getSortFieldAndDirection(oldSortParam);
125
+
126
+ // Use the state-specified direction if available, or extract one from the param if not
127
+ if (!state.sortDirection) prefix = this.sortDirectionPrefix(direction);
128
+
129
+ if (field) {
130
+ newParams.set('sort', `${prefix}${field}`);
131
+ } else {
132
+ newParams.set('sort', oldSortParam);
133
+ }
134
+ } else if (sortOption.shownInURL) {
135
+ // Otherwise, use the canonical API form of the sort option
136
+ const canonicalApiSort = sortOption.urlNames[0];
137
+ newParams.set('sort', `${prefix}${canonicalApiSort}`);
138
+ }
128
139
  }
129
140
 
130
141
  if (state.selectedFacets) {
@@ -197,7 +208,7 @@ export class RestorationStateHandler
197
208
  query: state.baseQuery,
198
209
  searchType: state.searchType,
199
210
  page: state.currentPage,
200
- sort: state.sortParam,
211
+ sort: { field: state.selectedSort, direction: state.sortDirection },
201
212
  minDate: state.minSelectedDate,
202
213
  maxDate: state.maxSelectedDate,
203
214
  facets: state.selectedFacets,
@@ -261,29 +272,13 @@ export class RestorationStateHandler
261
272
  }
262
273
 
263
274
  if (sortQuery) {
264
- // check for two different sort formats: `date desc` and `-date`
265
- const hasSpace = sortQuery.indexOf(' ') > -1;
266
- if (hasSpace) {
267
- const [field, direction] = sortQuery.split(' ');
268
- const metadataField = URLFieldToSortField[field as URLSortField];
269
-
270
- if (metadataField) {
271
- restorationState.selectedSort = metadataField;
272
- }
273
- if (direction === 'desc' || direction === 'asc') {
274
- restorationState.sortDirection = direction as SortDirection;
275
- }
276
- } else {
277
- const direction = sortQuery.startsWith('-') ? 'desc' : 'asc';
278
- const field = sortQuery.startsWith('-')
279
- ? sortQuery.slice(1)
280
- : sortQuery;
281
-
282
- const metadataField = URLFieldToSortField[field as URLSortField];
283
- if (metadataField) {
284
- restorationState.selectedSort = metadataField;
285
- restorationState.sortDirection = direction as SortDirection;
286
- }
275
+ const { field, direction } = this.getSortFieldAndDirection(sortQuery);
276
+
277
+ const sortOption = sortOptionFromAPIString(field);
278
+ restorationState.selectedSort = sortOption.field;
279
+
280
+ if (['asc', 'desc'].includes(direction)) {
281
+ restorationState.sortDirection = direction as SortDirection;
287
282
  }
288
283
  }
289
284
 
@@ -296,6 +291,13 @@ export class RestorationStateHandler
296
291
  // which we want to normalize to 'creator', 'language', etc. if redirected here.
297
292
  field = field.replace(/Sorter$/, '');
298
293
 
294
+ // Legacy search also allowed a form of negative faceting like `and[]=-collection:foo`
295
+ // which we want to normalize to a not[] param instead
296
+ if (field.startsWith('-')) {
297
+ facetNots.push(and.slice(1));
298
+ return;
299
+ }
300
+
299
301
  switch (field) {
300
302
  case 'year': {
301
303
  const [minDate, maxDate] = value.split(' TO ');
@@ -354,7 +356,31 @@ export class RestorationStateHandler
354
356
  return restorationState;
355
357
  }
356
358
 
357
- // remove optional opening and closing quotes from a string
359
+ /**
360
+ * Converts a URL sort param into a field/direction pair, if possible.
361
+ * Either or both may be undefined if the param is not in a recognized format.
362
+ */
363
+ private getSortFieldAndDirection(sortParam: string) {
364
+ // check for two different sort formats: `date desc` and `-date`
365
+ const hasSpace = sortParam.indexOf(' ') > -1;
366
+ let field;
367
+ let direction;
368
+ if (hasSpace) {
369
+ [field, direction] = sortParam.split(' ');
370
+ } else {
371
+ field = sortParam.startsWith('-') ? sortParam.slice(1) : sortParam;
372
+ direction = sortParam.startsWith('-') ? 'desc' : 'asc';
373
+ }
374
+
375
+ return { field, direction };
376
+ }
377
+
378
+ /** Returns the `-` prefix for `desc` sort, or the empty string otherwise. */
379
+ private sortDirectionPrefix(sortDirection?: string) {
380
+ return sortDirection === 'desc' ? '-' : '';
381
+ }
382
+
383
+ /** Remove optional opening and closing quotes from a string */
358
384
  private stripQuotes(value: string): string {
359
385
  if (value.startsWith('"') && value.endsWith('"')) {
360
386
  return value.substring(1, value.length - 1);
@@ -16,11 +16,10 @@ import type { IaDropdown, optionInterface } from '@internetarchive/ia-dropdown';
16
16
  import type { SortDirection } from '@internetarchive/search-service';
17
17
  import {
18
18
  CollectionDisplayMode,
19
- DefaultSortDirection,
20
19
  PrefixFilterCounts,
21
20
  PrefixFilterType,
21
+ SORT_OPTIONS,
22
22
  SortField,
23
- SortFieldDisplayName,
24
23
  } from '../models';
25
24
  import './alpha-bar';
26
25
 
@@ -153,7 +152,8 @@ export class SortFilterBar
153
152
  }
154
153
 
155
154
  if (changed.has('selectedSort') && this.sortDirection === null) {
156
- this.sortDirection = DefaultSortDirection[this.finalizedSortField];
155
+ const sortOption = SORT_OPTIONS[this.finalizedSortField];
156
+ this.sortDirection = sortOption.defaultSortDirection;
157
157
  }
158
158
 
159
159
  if (changed.has('selectedTitleFilter') && this.selectedTitleFilter) {
@@ -274,7 +274,7 @@ export class SortFilterBar
274
274
  return html`
275
275
  <button
276
276
  class="sort-direction-selector"
277
- ?disabled=${this.finalizedSortField === SortField.relevance}
277
+ ?disabled=${!this.canChangeSortDirection}
278
278
  @click=${this.handleSortDirectionClicked}
279
279
  >
280
280
  <span class="sr-only">${srLabel}</span>
@@ -285,8 +285,8 @@ export class SortFilterBar
285
285
 
286
286
  /** Template to render the sort direction button's icon in the correct current state */
287
287
  private get sortDirectionIcon(): TemplateResult {
288
- // For relevance sort, show a fully disabled icon
289
- if (this.finalizedSortField === SortField.relevance) {
288
+ // Show a fully disabled icon for sort options without direction support
289
+ if (!this.canChangeSortDirection) {
290
290
  return html`<div class="sort-direction-icon">${sortDisabledIcon}</div>`;
291
291
  }
292
292
 
@@ -354,9 +354,9 @@ export class SortFilterBar
354
354
 
355
355
  /** The template to render all the sort options in mobile view */
356
356
  private get mobileSortSelectorTemplate() {
357
- const isDisplayableField = (field: string) =>
358
- field !== SortField.default &&
359
- (field !== SortField.relevance || this.showRelevance);
357
+ const displayedOptions = Object.values(SORT_OPTIONS)
358
+ .filter(opt => opt.shownInSortBar)
359
+ .filter(opt => this.showRelevance || opt.field !== SortField.relevance);
360
360
 
361
361
  return html`
362
362
  <div
@@ -364,13 +364,13 @@ export class SortFilterBar
364
364
  class=${this.mobileSelectorVisible ? 'visible' : 'hidden'}
365
365
  >
366
366
  ${this.getSortDropdown({
367
- displayName: html`${SortFieldDisplayName[this.finalizedSortField] ??
368
- 'Relevance'}`,
367
+ displayName: html`${SORT_OPTIONS[this.finalizedSortField]
368
+ .displayName}`,
369
369
  id: 'mobile-dropdown',
370
370
  selected: true,
371
- dropdownOptions: Object.keys(SortField)
372
- .filter(field => isDisplayableField(field))
373
- .map(field => this.getDropdownOption(field as SortField)),
371
+ dropdownOptions: displayedOptions.map(opt =>
372
+ this.getDropdownOption(opt.field)
373
+ ),
374
374
  selectedOption: this.finalizedSortField,
375
375
  onOptionSelected: this.mobileSortChanged,
376
376
  onDropdownClick: () => {
@@ -408,7 +408,8 @@ export class SortFilterBar
408
408
  ): TemplateResult {
409
409
  const isSelected =
410
410
  options?.selected ?? this.finalizedSortField === sortField;
411
- const displayName = options?.displayName ?? SortFieldDisplayName[sortField];
411
+ const displayName =
412
+ options?.displayName ?? SORT_OPTIONS[sortField].displayName;
412
413
  return html`
413
414
  <button
414
415
  class=${isSelected ? 'selected' : nothing}
@@ -488,7 +489,7 @@ export class SortFilterBar
488
489
  },
489
490
  label: html`
490
491
  <span class="dropdown-option-label">
491
- ${SortFieldDisplayName[sortField]}
492
+ ${SORT_OPTIONS[sortField].displayName}
492
493
  </span>
493
494
  `,
494
495
  };
@@ -692,7 +693,8 @@ export class SortFilterBar
692
693
  private setSelectedSort(sort: SortField) {
693
694
  this.selectedSort = sort;
694
695
  // Apply this field's default sort direction
695
- this.sortDirection = DefaultSortDirection[this.selectedSort];
696
+ const sortOption = SORT_OPTIONS[sort];
697
+ this.sortDirection = sortOption.defaultSortDirection;
696
698
  this.emitSortChangedEvent();
697
699
  }
698
700
 
@@ -710,6 +712,11 @@ export class SortFilterBar
710
712
  : this.sortDirection;
711
713
  }
712
714
 
715
+ /** Whether the sort direction button should be enabled for the current sort */
716
+ private get canChangeSortDirection(): boolean {
717
+ return SORT_OPTIONS[this.finalizedSortField].canSetDirection;
718
+ }
719
+
713
720
  /**
714
721
  * There are four date sort options.
715
722
  *
@@ -757,11 +764,11 @@ export class SortFilterBar
757
764
  * @memberof SortFilterBar
758
765
  */
759
766
  private get dateSortField(): string {
760
- const defaultSort = SortFieldDisplayName[SortField.date];
761
- const name = this.dateOptionSelected
762
- ? SortFieldDisplayName[this.finalizedSortField] ?? defaultSort
763
- : defaultSort;
764
- return name;
767
+ const defaultDateSort = SORT_OPTIONS[SortField.date];
768
+ const currentDateSort = this.dateOptionSelected
769
+ ? SORT_OPTIONS[this.finalizedSortField] ?? defaultDateSort
770
+ : defaultDateSort;
771
+ return currentDateSort.displayName;
765
772
  }
766
773
 
767
774
  /**
@@ -773,11 +780,11 @@ export class SortFilterBar
773
780
  * @memberof SortFilterBar
774
781
  */
775
782
  private get viewSortField(): string {
776
- const defaultSort = SortFieldDisplayName[SortField.weeklyview];
777
- const name = this.viewOptionSelected
778
- ? SortFieldDisplayName[this.finalizedSortField] ?? defaultSort
779
- : defaultSort;
780
- return name;
783
+ const defaultViewSort = SORT_OPTIONS[SortField.weeklyview];
784
+ const currentViewSort = this.viewOptionSelected
785
+ ? SORT_OPTIONS[this.finalizedSortField] ?? defaultViewSort
786
+ : defaultViewSort;
787
+ return currentViewSort.displayName;
781
788
  }
782
789
 
783
790
  private get titleSelectorBar() {