Changes for page Solr Search
Last modified by Сергей Коршунов on 2025/12/29 15:30
From version 6.1
edited by Сергей Коршунов
on 2024/05/02 13:36
on 2024/05/02 13:36
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-search-solr-ui/16.3.0]
To version 8.1
edited by Сергей Коршунов
on 2025/12/29 15:30
on 2025/12/29 15:30
Change comment:
Install extension [org.xwiki.platform:xwiki-platform-search-solr-ui/17.10.0]
Summary
-
Objects (2 modified, 0 added, 0 removed)
Details
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -1,11 +1,7 @@ 1 1 require(['jquery', 'xwiki-events-bridge'], function($) { 2 2 var enhanceSearchResultHighlights = function() { 3 - varhighlights = $(this).removeClass('hidden').parent().prev('.search-result-highlights').addClass('preview');3 + const highlights = $(this).removeClass('hidden').prev('.search-result-highlights').addClass('preview'); 4 4 5 - // Workaround for IE8 which doesn't support :first-of-type CSS selector. 6 - highlights.find('.search-result-highlight').first().addClass('first').parent('dd').addClass('first') 7 - .prev('dt').addClass('first'); 8 - 9 9 $(this).one('click', function(event) { 10 10 event.preventDefault(); 11 11 $(event.target).remove(); ... ... @@ -37,10 +37,16 @@ 37 37 } 38 38 }; 39 39 40 - var addFacetValueCheckbox = function( index) {36 + var addFacetValueCheckbox = function() { 41 41 // Create an id unique to the facet value. 42 42 let facetContainer = $(this).parents('.search-facet').first(); 43 - $(this).attr('id', facetContainer.attr('data-name') + '-' + index.toString()); 39 + // We need the ids of the target facet elements to stay stable through the change, in order to refocus the item 40 + // that was clicked and triggered the change. 41 + // We use the JQuery index, so that id indexes are reset across facetContainers. 42 + // This allows for stable indexes: when reloading the facets after changing the filter for the target facet X, 43 + // the ids of all the checkbox for the target facet X will keep the same index. 44 + // This property is lost if multiple facets are changed in the query at once. 45 + $(this).attr('id', facetContainer.attr('data-name') + '-' + facetContainer.find('a.itemName').index($(this))); 44 44 // Initialize the checkbox. 45 45 let checkBox = $(document.createElement('input')).attr('type', 'checkbox'); 46 46 checkBox.attr('aria-labelledby', $(this).attr('id')); ... ... @@ -68,7 +68,7 @@ 68 68 updateExpandCollapseAllFacetsState(facetsContainer); 69 69 70 70 // Expand/Collapse toggle for each facet. 71 - facetsContainer.find('.facet-toggle r').on('click', function(event) {73 + facetsContainer.find('.facet-toggle').on('click', function(event) { 72 72 $(event.target).parents('.search-facet').toggleClass('expanded'); 73 73 updateExpandCollapseAllFacetsState(facetsContainer); 74 74 }); ... ... @@ -83,6 +83,11 @@ 83 83 var queryIndex = url.indexOf('?'); 84 84 return queryIndex < 0 ? '' : url.substr(queryIndex + 1); 85 85 }; 88 + 89 + let removeQueryString = function(url) { 90 + let queryIndex = url.indexOf('?'); 91 + return queryIndex < 0 ? url : url.substring(0, queryIndex); 92 + }; 86 86 87 87 var getSearchUIState = function() { 88 88 var expandedFacets = []; ... ... @@ -112,7 +112,9 @@ 112 112 113 113 var searchRequest = null; 114 114 115 - var pushSearchUIState = function(viewURL) { 122 + // changeTargetSelector is the CSS selector of the facet anchor that initiated this reload. ' 123 + // We expect the focus to go back to this place once the UI is reloaded. 124 + var pushSearchUIState = function(viewURL, changeTargetSelector) { 116 116 // If there is a request in progress, abort it to prevent its callback from being called. 117 117 searchRequest && searchRequest.abort(); 118 118 $('.search-ui').attr('aria-busy', true); ... ... @@ -125,6 +125,7 @@ 125 125 window.history.replaceState && window.history.replaceState(state, document.title); 126 126 // Make sure the browser address bar reflects the new state (and thus the new state can be bookmarked). 127 127 window.history.pushState && window.history.pushState(state, document.title, viewURL); 137 + document.querySelector(changeTargetSelector)?.focus(); 128 128 }); 129 129 }; 130 130 ... ... @@ -131,12 +131,12 @@ 131 131 var reloadSearchUI = function(event) { 132 132 event.preventDefault(); 133 133 var anchor = $(event.target).closest('a'); 134 - anchor.length && $(document).trigger('xwiki:search:update', anchor.attr('href'));144 + anchor.length && anchor.first().trigger('xwiki:search:update', anchor.attr('href')); 135 135 }; 136 136 137 137 // Others (e.g. a custom facet) can trigger a search UI update by firing this event. 138 138 $(document).on('xwiki:search:update', function(event, viewURL) { 139 - pushSearchUIState(viewURL); 149 + pushSearchUIState(viewURL, `[data-facetvalue='${CSS.escape(event.target.dataset.facetvalue)}']`); 140 140 }); 141 141 142 142 $(window).on('popstate', function(event) { ... ... @@ -176,10 +176,41 @@ 176 176 }); 177 177 178 178 var enhanceSearchUI = function() { 189 + // Enhance search options 190 + document.querySelectorAll('input.options-item').forEach(function (option){ 191 + option.addEventListener('change', function() { 192 + let queryFieldName = this.getAttribute('data-query-name'); 193 + let queryField = document.querySelector('input[name="' + queryFieldName + '"]'); 194 + queryField.value = this.checked ? 'true' : 'false'; 195 + // We want to build the URL the same way it's done in the velocimacro #extendQueryString 196 + // We retrieve the search parameters of the latest request 197 + let params = new URLSearchParams(window.location.search); 198 + let formParams = new URLSearchParams(new FormData(this.form)); 199 + // We replace the existing parameters with their value from the form 200 + for (let param of formParams.keys()) { 201 + if (params.has(param)) params.delete(param); 202 + for (let paramValue of formParams.getAll(param)) { 203 + params.append(param,paramValue); 204 + } 205 + } 206 + pushSearchUIState(window.location.pathname + '?' + params.toString(), 207 + 'input[data-query-name="' + queryFieldName + '"]'); 208 + }); 209 + }); 210 + // Enhance search result sorting 211 + document.querySelectorAll('.search-results-sort select#sort-by-input, ' + 212 + '.search-results-sort input#sort-order-input').forEach(function (sortInput){ 213 + sortInput.addEventListener('change', function() { 214 + let baseURL = removeQueryString(this.form.getAttribute('action')); 215 + pushSearchUIState(baseURL + "?" + 216 + new URLSearchParams(new FormData(this.form)).toString(), 217 + 'input[data-query-name="' + this.getAttribute('name') + '"]'); 218 + }); 219 + }); 220 + 179 179 $('.search-result-highlightAll').each(enhanceSearchResultHighlights); 180 180 $('.search-facets').each(enhanceSearchFacets); 181 181 $([ 182 - '.search-results-sort a.sort-item', 183 183 '.search-options a.options-item', 184 184 '.pagination a', 185 185 '.controlPagination a',
- XWiki.StyleSheetExtension[0]
-
- Code
-
... ... @@ -1,7 +1,7 @@ 1 1 #template('colorThemeInit.vm') 2 2 3 3 /* Hide the 'Created by', 'Modified by' and 'Tags' document sections. */ 4 -.xdocLastModification, .skin-colibri#document-info, #xdocFooter {4 +.xdocLastModification, #xdocFooter { 5 5 display: none; 6 6 } 7 7 #document-title > h1 { ... ... @@ -9,86 +9,99 @@ 9 9 margin-bottom: 0; 10 10 } 11 11 12 +/** 13 + * Layout for the search bar 14 + */ 15 +.search-bar { 16 + margin: .5em 0; 17 +} 18 + 19 +@media (min-width: 768px) { 20 + .search-bar { 21 + max-width: 50%; 22 + } 23 +} 24 + 12 12 /** 13 - * Searchform26 + * Layout for search controls (sort + options) 14 14 */ 15 15 16 -.skin-colibri .search-form { 17 - /* There is no space after the title in Colibri. */ 18 - margin-top: 1.5em; 29 +.search-results-controls { 30 + display: flex; 31 + flex-wrap: wrap; 32 + gap: 1em; 33 + 34 + & label { 35 + /* Reset the styles of the labels. */ 36 + margin: 0; 37 + font-weight: unset; 38 + } 19 19 } 20 20 21 -.skin-colibri .search-form input[type="search"] { 22 - /* Colibri doesn't have the grid system. */ 23 - width: 50%; 41 +.search-results-sort, 42 +.search-options, .search-options > ul, .search-options > ul > li, 43 +.search-options > ul > li > label { 44 + display: flex; 45 + gap: .4em; 46 + align-items: center; 24 24 } 25 25 26 26 /** 27 27 * Sort 28 28 */ 52 +/* This select should be especially lightweight on the UI. We're removing the default border and shadow. */ 53 +#sort-by-input { 54 + cursor: pointer; 29 29 30 -ul.search-results-sort { 31 - color: $theme.textSecondaryColor; 32 - font-size: .9em; 33 - padding: 5px 0 2px 0; 56 + &:hover, &:focus { 57 + cursor: pointer; 58 + } 59 +} 60 + 61 +/* This checkbox input should be styled as a switch between ascending and descending states. */ 62 +.search-results-sort #sort-order-input { 63 + /* Hide the default checkbox. We rely on the style of the icon in its label to make it work. */ 64 + appearance: none; 34 34 margin: 0; 35 35 } 36 -.search-results-sort li { 37 - display: inline; 38 - list-style-type: none; 39 - padding-left: 1.5em; 67 + 68 +#sort-by-input, .search-results-sort label:has(>#sort-order-input) { 69 + /* We want most of the styles from "form-control", but not the shadow that comes from bootstrap. */ 70 + box-shadow: none; 71 + & :hover,& :focus,& :focus-within { 72 + border-color: var(--input-border-focus); 73 + } 40 40 } 41 -.search-results-sort li:first-of-type { 42 - padding: 0; 75 + 76 +/* Flip the second icon so that it's the arrow going up and down. */ 77 +.search-results-sort label #sort-order-input + * + * { 78 + transform: scaleY(-1); 43 43 } 44 -a.sort-item { 45 - color: inherit; 46 - text-decoration: none; 80 + 81 +/* When the box is checked, we want to hide the first icon. 82 +When the box is not checked, we want to hide the second icon. */ 83 +.search-results-sort label #sort-order-input:checked + *, 84 +.search-results-sort label #sort-order-input:not(:checked) + * + * { 85 + display: none; 47 47 } 48 -a.sort-item:hover { 49 - color: $theme.linkColor; 50 - text-decoration: underline; 51 -} 52 -a.sort-item.active, a.sort-item.active:hover { 53 - font-weight: bold; 54 - color: $theme.textColor; 55 - text-decoration: none; 56 -} 57 -.sort-item-order { 58 - margin-left: .3em; 59 -} 60 60 61 61 /** 62 62 * Options 63 63 */ 64 64 65 -ul.search-options { 66 - color: $theme.textSecondaryColor; 67 - font-size: .9em; 68 - padding: 5px 0 2px 0; 92 +.search-options ul { 69 69 margin: 0; 70 -} 71 -.search-options li { 72 - display: inline; 73 - list-style-type: none; 74 - padding-left: 1.5em; 75 -} 76 -.search-options li:first-of-type { 77 77 padding: 0; 95 + 96 + & input[type="checkbox"] { 97 + position: unset; 98 + margin: 0; 99 + } 100 + 101 + & label { 102 + margin-right: 1em; 103 + } 78 78 } 79 -a.options-item { 80 - color: inherit; 81 - text-decoration: none; 82 -} 83 -a.options-item:hover { 84 - color: $theme.linkColor; 85 - text-decoration: underline; 86 -} 87 -a.options-item.active, a.options-item.active:hover{ 88 - font-weight: bold; 89 - color: $theme.textColor; 90 - text-decoration: none; 91 -} 92 92 93 93 /** 94 94 * Search Results ... ... @@ -98,12 +98,6 @@ 98 98 margin-top: 1em; 99 99 } 100 100 101 -/* Colibri skin doesn't have the grid system. */ 102 -.skin-colibri .search-results-left { 103 - margin: 0.5em 20em 0.5em 0; 104 - padding: 0.5em 0.5em 0.5em 0; 105 -} 106 - 107 107 .search-results { 108 108 padding: .3em 0 .8em 0; 109 109 } ... ... @@ -167,6 +167,8 @@ 167 167 168 168 dl.search-result-highlights > dt { 169 169 margin-top: .3em; 177 + color: var(--text-muted); 178 + font-weight: var(--font-weight-semibold); 170 170 } 171 171 172 172 blockquote.search-result-highlight { ... ... @@ -187,11 +187,6 @@ 187 187 font-weight: bold; 188 188 } 189 189 190 -dl.search-result-highlights > dt { 191 - color: $theme.textSecondaryColor; 192 - font-weight: normal; 193 -} 194 - 195 195 dl.search-result-highlights.preview dt, 196 196 dl.search-result-highlights.preview dd > * { 197 197 display: none; ... ... @@ -198,17 +198,10 @@ 198 198 } 199 199 200 200 dl.search-result-highlights.preview dt:first-of-type, 201 -dl.search-result-highlights.preview dd:first-of-type blockquote:first-of-type, 202 -/* Workaround for IE8 which doesn't support :first-of-type CSS selector. */ 203 -dl.search-result-highlights.preview dt.first, 204 -dl.search-result-highlights.preview dd.first blockquote.first { 205 +dl.search-result-highlights.preview dd:first-of-type blockquote:first-of-type { 205 205 display: block; 206 206 } 207 207 208 -a.search-result-highlightAll:after { 209 - content: ' \bb'; 210 -} 211 - 212 212 .search-result-debug { 213 213 white-space: pre; 214 214 } ... ... @@ -217,40 +217,23 @@ 217 217 * Facets 218 218 */ 219 219 220 -.search-facets { 221 - background-color: $theme.backgroundSecondaryColor; 222 - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); 223 - /* Leave space for the bottom shadow. */ 224 - margin-bottom: 1em; 225 - border-radius: 7px; 217 +.search-facets-header{ 218 + border-bottom: 1px solid var(--xwiki-border-color); 226 226 } 227 -/* Colibri skin doesn't have the grid system. */ 228 -.skin-colibri .search-facets { 229 - float: right; 230 - max-width: 19.5em; 231 - width: 19.5em; 232 -} 233 233 234 -.search-facets-header, 235 -.search-facets-actions, 236 -.search-facet { 237 - border-bottom: 1px solid $theme.borderColor; 238 - border-top: 1px solid $theme.pageContentBackgroundColor; 239 - position: relative; 221 +.search-facets-header strong { 222 + font-size: 1.25em; 240 240 } 241 241 242 242 .search-facets-header, 243 243 .search-facets-actions { 244 - padding: 0.5em1em;227 + padding: .5em 0; 245 245 } 229 + 246 246 .search-facet { 247 - padding: 0.5em.8em;231 + padding: .2em 0; 248 248 } 249 249 250 -.search-facets-header { 251 - border-top: none; 252 -} 253 - 254 254 .search-facets-header > p, 255 255 .search-facets-actions > p { 256 256 /* The wiki syntax generates paragraphs which have bottom margin. */ ... ... @@ -261,16 +261,6 @@ 261 261 font-size: .8em; 262 262 } 263 263 264 -.search-facets-actions a { 265 - color: $theme.textSecondaryColor; 266 - text-decoration: none; 267 -} 268 - 269 -.search-facets-actions a:hover { 270 - color: $theme.linkColor; 271 - text-decoration: underline; 272 -} 273 - 274 274 .search-facets-action-collapseAll, 275 275 .search-facets-action-expandAll { 276 276 float: right; ... ... @@ -282,48 +282,37 @@ 282 282 margin: 0; 283 283 } 284 284 285 -.search-facet:last-of-type { 286 - border-bottom: none; 255 +.search-facet-header, .search-facet-body { 256 + padding-left: .5em; 257 + border: 1px solid var(--xwiki-border-color); 258 + border-radius: var(--border-radius-small); 287 287 } 288 288 289 289 .search-facet-header { 262 + background-color: var(--xwiki-background-secondary-color); 263 +} 264 + 265 +.search-facet-header label { 290 290 color: $theme.titleColor; 291 291 cursor: pointer; 292 - line-height: 1.4em; 293 - margin: 0 .2em; 294 294 display: flex; 295 295 justify-content: space-between; 296 - position:relative;270 + align-items: center; 297 297 } 298 298 299 -.search-facet-header:after { 300 - border-bottom: 1px dotted $theme.pageContentBackgroundColor; 301 - border-top: 1px dotted $theme.borderColor; 302 - clear: both; 303 - content: ""; 304 - display: block; 305 - height: 0; 273 +.search-facet-body { 274 + opacity: 0; 275 + visibility: hidden; /* This makes sure the element is removed from the accessibility tree. */ 276 + 306 306 position: absolute; 307 - right: 0; 308 - bottom: 0; 309 - width: 100%; 278 + transform: translateY(-10px); /* Start the animation slightly above */ 279 + padding-top: .5em; 280 + 281 + border-top-width: 0; 282 + border-top-left-radius: 0; 283 + border-top-right-radius: 0; 310 310 } 311 311 312 -.search-facet:last-of-type .search-facet-header:after { 313 - border: medium none; 314 -} 315 - 316 -.search-facet.expanded:last-of-type .search-facet-header:after { 317 - border-bottom: 1px dotted $theme.pageContentBackgroundColor; 318 - border-top: 1px dotted $theme.borderColor; 319 -} 320 - 321 -.search-facet-body { 322 - overflow: hidden; /* required for effect */ 323 - display: none; 324 - margin-top: .5em; 325 -} 326 - 327 327 .search-facet-body ul, 328 328 .search-facet-body ol { 329 329 font-size: .9em; ... ... @@ -332,37 +332,55 @@ 332 332 .search-facet-body li { 333 333 display: flex; 334 334 flex-wrap: wrap; 335 - padding: . 1em .2em;294 + padding: .3em .5em; 336 336 } 337 337 338 -.search-facet .search-facet-header .facet-toggle r, button.facet-value-toggler{297 +.search-facet .search-facet-header .facet-toggle, button.facet-value-toggle { 339 339 background: transparent; 340 340 transition: background-color .2s ease-in-out; 341 341 } 342 342 343 -.search-facet .search-facet-header .facet-toggle r:active, button.facet-value-toggler:active {302 +.search-facet .search-facet-header .facet-toggle:active, button.facet-value-toggle:active { 344 344 box-shadow: unset; 345 345 } 346 346 347 -.search-facet .search-facet-header .facet-toggle r> span, button.facet-value-toggler> span,348 -.search-facet .search-facet-header .facet-toggle r> img, button.facet-value-toggler> img {306 +.search-facet .search-facet-header .facet-toggle > span, button.facet-value-toggle > span, 307 +.search-facet .search-facet-header .facet-toggle > img, button.facet-value-toggle > img { 349 349 transform: rotate(90deg); 350 350 } 351 351 352 -.search-facet.expanded .search-facet-header .facet-toggle r> span, .expanded > button.facet-value-toggler> span,353 -.search-facet.expanded .search-facet-header .facet-toggle r> img, .expanded > button.facet-value-toggler> img {311 +.search-facet.expanded .search-facet-header .facet-toggle > span, .expanded > button.facet-value-toggle > span, 312 +.search-facet.expanded .search-facet-header .facet-toggle > img, .expanded > button.facet-value-toggle > img { 354 354 transform: rotate(0deg); 355 355 } 356 356 357 -@media not(prefers-reduced-motion) {358 - .search-facet .search-facet-header .facet-toggle r> span, button.facet-value-toggler> span,359 - .search-facet .search-facet-header .facet-toggle r> img, button.facet-value-toggler> img {316 +@media (prefers-reduced-motion: no-preference) { 317 + .search-facet .search-facet-header .facet-toggle > span, button.facet-value-toggle > span, 318 + .search-facet .search-facet-header .facet-toggle > img, button.facet-value-toggle > img { 360 360 transition: transform 0.2s ease; 361 361 } 321 + 322 + .search-facet-body { 323 + transition: opacity 0.3s ease, transform 0.3s ease; 324 + } 362 362 } 363 363 364 -.search-facet.expanded .search-facet-body { 365 - display: block; 327 +.search-facet.expanded { 328 + & .search-facet-header { 329 + border-bottom-width: 0; 330 + border-bottom-left-radius: 0; 331 + border-bottom-right-radius: 0; 332 + 333 + & label { 334 + font-weight: var(--font-weight-semibold); 335 + } 336 + } 337 + & .search-facet-body { 338 + opacity: 1; 339 + position: unset; /* This element should be positioned normally when shown. */ 340 + visibility: visible; 341 + transform: translateY(0); 342 + } 366 366 } 367 367 368 368 .search-facet-body ul, .search-facet-body ul.users { ... ... @@ -373,10 +373,6 @@ 373 373 margin: .5em 0; 374 374 } 375 375 376 -.search-facet-body li:hover { 377 - background-color: $theme.highlightColor; 378 -} 379 - 380 380 .search-facet-body input[type="checkbox"] { 381 381 margin: .2em 0; 382 382 } ... ... @@ -401,7 +401,8 @@ 401 401 } 402 402 403 403 .search-facet-body .itemName, 404 -.search-facet-body .facet-value-toggler, 377 +.search-facet-body .itemNameempty, 378 +.search-facet-body .facet-value-toggle, 405 405 .search-facet-body .more { 406 406 /* Remove link styling */ 407 407 color: $theme.textColor; ... ... @@ -419,8 +419,11 @@ 419 419 } 420 420 421 421 .search-facet-body .itemCount { 422 - padding: .1em 0;396 + padding: .1em .5em; 423 423 margin-left: auto; 398 + background-color: var(--nav-link-hover-bg); 399 + /* We want those item count blocks to be pill shaped. */ 400 + border-radius: 1em / 50%; 424 424 } 425 425 426 426 @media (max-width: 768px) { ... ... @@ -440,17 +440,6 @@ 440 440 } 441 441 442 442 /** 443 - * Fix the breadcrumb in Colibri skin. 444 - */ 445 -.skin-colibri .breadcrumb > li { 446 - display: inline; 447 -} 448 -.skin-colibri .breadcrumb > li + li:before { 449 - color: $theme.textSecondaryColor; 450 - content: ' \00BB '; 451 -} 452 - 453 -/** 454 454 * Miscellaneous 455 455 */ 456 456 ... ... @@ -459,13 +459,3 @@ 459 459 padding-left: 0; 460 460 } 461 461 462 -.paginationFilter .resultsNo, 463 -.paginationFilter .controlPagination, 464 -.paginationFilter .pagination { 465 - line-height: 22px; 466 -} 467 - 468 -.iconRSS { 469 - background: url("$xwiki.getSkinFile('icons/silk/feed.png')") no-repeat scroll 0 0 transparent; 470 - padding-left: 20px; 471 -}