0 Votes

Changes for page Solr Search

Last modified by Сергей Коршунов on 2025/12/29 15:30

From version 8.1
edited by Сергей Коршунов
on 2025/12/29 15:30
Change comment: Install extension [org.xwiki.platform:xwiki-platform-search-solr-ui/17.10.0]
To version 6.1
edited by Сергей Коршунов
on 2024/05/02 13:36
Change comment: Install extension [org.xwiki.platform:xwiki-platform-search-solr-ui/16.3.0]

Summary

Details

XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,7 +1,11 @@
1 1  require(['jquery', 'xwiki-events-bridge'], function($) {
2 2   var enhanceSearchResultHighlights = function() {
3 - const highlights = $(this).removeClass('hidden').prev('.search-result-highlights').addClass('preview');
3 + var highlights = $(this).removeClass('hidden').parent().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 +
5 5   $(this).one('click', function(event) {
6 6   event.preventDefault();
7 7   $(event.target).remove();
... ... @@ -33,16 +33,10 @@
33 33   }
34 34   };
35 35  
36 - var addFacetValueCheckbox = function() {
40 + var addFacetValueCheckbox = function(index) {
37 37   // Create an id unique to the facet value.
38 38   let facetContainer = $(this).parents('.search-facet').first();
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)));
43 + $(this).attr('id', facetContainer.attr('data-name') + '-' + index.toString());
46 46   // Initialize the checkbox.
47 47   let checkBox = $(document.createElement('input')).attr('type', 'checkbox');
48 48   checkBox.attr('aria-labelledby', $(this).attr('id'));
... ... @@ -70,7 +70,7 @@
70 70   updateExpandCollapseAllFacetsState(facetsContainer);
71 71  
72 72   // Expand/Collapse toggle for each facet.
73 - facetsContainer.find('.facet-toggle').on('click', function(event) {
71 + facetsContainer.find('.facet-toggler').on('click', function(event) {
74 74   $(event.target).parents('.search-facet').toggleClass('expanded');
75 75   updateExpandCollapseAllFacetsState(facetsContainer);
76 76   });
... ... @@ -85,11 +85,6 @@
85 85   var queryIndex = url.indexOf('?');
86 86   return queryIndex < 0 ? '' : url.substr(queryIndex + 1);
87 87   };
88 -
89 - let removeQueryString = function(url) {
90 - let queryIndex = url.indexOf('?');
91 - return queryIndex < 0 ? url : url.substring(0, queryIndex);
92 - };
93 93  
94 94   var getSearchUIState = function() {
95 95   var expandedFacets = [];
... ... @@ -119,9 +119,7 @@
119 119  
120 120   var searchRequest = null;
121 121  
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) {
115 + var pushSearchUIState = function(viewURL) {
125 125   // If there is a request in progress, abort it to prevent its callback from being called.
126 126   searchRequest && searchRequest.abort();
127 127   $('.search-ui').attr('aria-busy', true);
... ... @@ -134,7 +134,6 @@
134 134   window.history.replaceState && window.history.replaceState(state, document.title);
135 135   // Make sure the browser address bar reflects the new state (and thus the new state can be bookmarked).
136 136   window.history.pushState && window.history.pushState(state, document.title, viewURL);
137 - document.querySelector(changeTargetSelector)?.focus();
138 138   });
139 139   };
140 140  
... ... @@ -141,12 +141,12 @@
141 141   var reloadSearchUI = function(event) {
142 142   event.preventDefault();
143 143   var anchor = $(event.target).closest('a');
144 - anchor.length && anchor.first().trigger('xwiki:search:update', anchor.attr('href'));
134 + anchor.length && $(document).trigger('xwiki:search:update', anchor.attr('href'));
145 145   };
146 146  
147 147   // Others (e.g. a custom facet) can trigger a search UI update by firing this event.
148 148   $(document).on('xwiki:search:update', function(event, viewURL) {
149 - pushSearchUIState(viewURL, `[data-facetvalue='${CSS.escape(event.target.dataset.facetvalue)}']`);
139 + pushSearchUIState(viewURL);
150 150   });
151 151  
152 152   $(window).on('popstate', function(event) {
... ... @@ -186,41 +186,10 @@
186 186   });
187 187  
188 188   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 -
221 221   $('.search-result-highlightAll').each(enhanceSearchResultHighlights);
222 222   $('.search-facets').each(enhanceSearchFacets);
223 223   $([
182 + '.search-results-sort a.sort-item',
224 224   '.search-options a.options-item',
225 225   '.pagination a',
226 226   '.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, #xdocFooter {
4 +.xdocLastModification, .skin-colibri #document-info, #xdocFooter {
5 5   display: none;
6 6  }
7 7  #document-title > h1 {
... ... @@ -9,99 +9,86 @@
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 -
25 25  /**
26 - * Layout for search controls (sort + options)
13 + * Search form
27 27   */
28 28  
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 - }
16 +.skin-colibri .search-form {
17 + /* There is no space after the title in Colibri. */
18 + margin-top: 1.5em;
39 39  }
40 40  
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;
21 +.skin-colibri .search-form input[type="search"] {
22 + /* Colibri doesn't have the grid system. */
23 + width: 50%;
47 47  }
48 48  
49 49  /**
50 50   * Sort
51 51   */
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;
55 55  
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;
30 +ul.search-results-sort {
31 + color: $theme.textSecondaryColor;
32 + font-size: .9em;
33 + padding: 5px 0 2px 0;
65 65   margin: 0;
66 66  }
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 - }
36 +.search-results-sort li {
37 + display: inline;
38 + list-style-type: none;
39 + padding-left: 1.5em;
74 74  }
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);
41 +.search-results-sort li:first-of-type {
42 + padding: 0;
79 79  }
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;
44 +a.sort-item {
45 + color: inherit;
46 + text-decoration: none;
86 86  }
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 +}
87 87  
88 88  /**
89 89   * Options
90 90   */
91 91  
92 -.search-options ul {
65 +ul.search-options {
66 + color: $theme.textSecondaryColor;
67 + font-size: .9em;
68 + padding: 5px 0 2px 0;
93 93   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 {
94 94   padding: 0;
95 -
96 - & input[type="checkbox"] {
97 - position: unset;
98 - margin: 0;
99 - }
100 -
101 - & label {
102 - margin-right: 1em;
103 - }
104 104  }
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 +}
105 105  
106 106  /**
107 107   * Search Results
... ... @@ -111,6 +111,12 @@
111 111   margin-top: 1em;
112 112  }
113 113  
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 +
114 114  .search-results {
115 115   padding: .3em 0 .8em 0;
116 116  }
... ... @@ -174,8 +174,6 @@
174 174  
175 175  dl.search-result-highlights > dt {
176 176   margin-top: .3em;
177 - color: var(--text-muted);
178 - font-weight: var(--font-weight-semibold);
179 179  }
180 180  
181 181  blockquote.search-result-highlight {
... ... @@ -196,6 +196,11 @@
196 196   font-weight: bold;
197 197  }
198 198  
190 +dl.search-result-highlights > dt {
191 + color: $theme.textSecondaryColor;
192 + font-weight: normal;
193 +}
194 +
199 199  dl.search-result-highlights.preview dt,
200 200  dl.search-result-highlights.preview dd > * {
201 201   display: none;
... ... @@ -202,10 +202,17 @@
202 202  }
203 203  
204 204  dl.search-result-highlights.preview dt:first-of-type,
205 -dl.search-result-highlights.preview dd:first-of-type blockquote: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 {
206 206   display: block;
207 207  }
208 208  
208 +a.search-result-highlightAll:after {
209 + content: ' \bb';
210 +}
211 +
209 209  .search-result-debug {
210 210   white-space: pre;
211 211  }
... ... @@ -214,23 +214,40 @@
214 214   * Facets
215 215   */
216 216  
217 -.search-facets-header{
218 - border-bottom: 1px solid var(--xwiki-border-color);
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;
219 219  }
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 +}
220 220  
221 -.search-facets-header strong {
222 - font-size: 1.25em;
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;
223 223  }
224 224  
225 225  .search-facets-header,
226 226  .search-facets-actions {
227 - padding: .5em 0;
244 + padding: 0.5em 1em;
228 228  }
229 -
230 230  .search-facet {
231 - padding: .2em 0;
247 + padding: 0.5em .8em;
232 232  }
233 233  
250 +.search-facets-header {
251 + border-top: none;
252 +}
253 +
234 234  .search-facets-header > p,
235 235  .search-facets-actions > p {
236 236   /* The wiki syntax generates paragraphs which have bottom margin. */
... ... @@ -241,6 +241,16 @@
241 241   font-size: .8em;
242 242  }
243 243  
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 +
244 244  .search-facets-action-collapseAll,
245 245  .search-facets-action-expandAll {
246 246   float: right;
... ... @@ -252,37 +252,48 @@
252 252   margin: 0;
253 253  }
254 254  
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);
285 +.search-facet:last-of-type {
286 + border-bottom: none;
259 259  }
260 260  
261 261  .search-facet-header {
262 - background-color: var(--xwiki-background-secondary-color);
263 -}
264 -
265 -.search-facet-header label {
266 266   color: $theme.titleColor;
267 267   cursor: pointer;
292 + line-height: 1.4em;
293 + margin: 0 .2em;
268 268   display: flex;
269 269   justify-content: space-between;
270 - align-items: center;
296 + position: relative;
271 271  }
272 272  
273 -.search-facet-body {
274 - opacity: 0;
275 - visibility: hidden; /* This makes sure the element is removed from the accessibility tree. */
276 -
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;
277 277   position: absolute;
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;
307 + right: 0;
308 + bottom: 0;
309 + width: 100%;
284 284  }
285 285  
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 +
286 286  .search-facet-body ul,
287 287  .search-facet-body ol {
288 288   font-size: .9em;
... ... @@ -291,55 +291,37 @@
291 291  .search-facet-body li {
292 292   display: flex;
293 293   flex-wrap: wrap;
294 - padding: .3em .5em;
335 + padding: .1em .2em;
295 295  }
296 296  
297 -.search-facet .search-facet-header .facet-toggle, button.facet-value-toggle {
338 +.search-facet .search-facet-header .facet-toggler, button.facet-value-toggler {
298 298   background: transparent;
299 299   transition: background-color .2s ease-in-out;
300 300  }
301 301  
302 -.search-facet .search-facet-header .facet-toggle:active, button.facet-value-toggle:active {
343 +.search-facet .search-facet-header .facet-toggler:active, button.facet-value-toggler:active {
303 303   box-shadow: unset;
304 304  }
305 305  
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 {
347 +.search-facet .search-facet-header .facet-toggler > span, button.facet-value-toggler > span,
348 +.search-facet .search-facet-header .facet-toggler > img, button.facet-value-toggler > img {
308 308   transform: rotate(90deg);
309 309  }
310 310  
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 {
352 +.search-facet.expanded .search-facet-header .facet-toggler > span, .expanded > button.facet-value-toggler > span,
353 +.search-facet.expanded .search-facet-header .facet-toggler > img, .expanded > button.facet-value-toggler > img {
313 313   transform: rotate(0deg);
314 314  }
315 315  
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 {
357 +@media not (prefers-reduced-motion) {
358 + .search-facet .search-facet-header .facet-toggler > span, button.facet-value-toggler > span,
359 + .search-facet .search-facet-header .facet-toggler > img, button.facet-value-toggler > img {
319 319   transition: transform 0.2s ease;
320 320   }
321 -
322 - .search-facet-body {
323 - transition: opacity 0.3s ease, transform 0.3s ease;
324 - }
325 325  }
326 326  
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 - }
364 +.search-facet.expanded .search-facet-body {
365 + display: block;
343 343  }
344 344  
345 345  .search-facet-body ul, .search-facet-body ul.users {
... ... @@ -350,6 +350,10 @@
350 350   margin: .5em 0;
351 351  }
352 352  
376 +.search-facet-body li:hover {
377 + background-color: $theme.highlightColor;
378 +}
379 +
353 353  .search-facet-body input[type="checkbox"] {
354 354   margin: .2em 0;
355 355  }
... ... @@ -374,8 +374,7 @@
374 374  }
375 375  
376 376  .search-facet-body .itemName,
377 -.search-facet-body .itemNameempty,
378 -.search-facet-body .facet-value-toggle,
404 +.search-facet-body .facet-value-toggler,
379 379  .search-facet-body .more {
380 380   /* Remove link styling */
381 381   color: $theme.textColor;
... ... @@ -393,11 +393,8 @@
393 393  }
394 394  
395 395  .search-facet-body .itemCount {
396 - padding: .1em .5em;
422 + padding: .1em 0;
397 397   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%;
401 401  }
402 402  
403 403  @media (max-width: 768px) {
... ... @@ -417,6 +417,17 @@
417 417  }
418 418  
419 419  /**
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 +/**
420 420   * Miscellaneous
421 421   */
422 422  
... ... @@ -425,3 +425,13 @@
425 425   padding-left: 0;
426 426  }
427 427  
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 +}