Split search services

This commit is contained in:
Georgy Litvinov 2021-11-02 11:21:30 +01:00
parent cdcc051d3f
commit e48b2d7e0f
9 changed files with 7122 additions and 14 deletions

View file

@ -37,7 +37,7 @@ public class UrlBuilder {
LOGIN("/login"),
LOGOUT("/logout"),
OBJECT_PROPERTY_EDIT("/propertyEdit"),
CUSTOMSEARCH("/customsearch"),
EXTENDED_SEARCH("/extendedsearch"),
SEARCH("/search"),
SITE_ADMIN("/siteAdmin"),
TERMS_OF_USE("/termsOfUse"),

View file

@ -304,7 +304,7 @@ public class FreemarkerConfigurationImpl extends Configuration {
urls.put("home", UrlBuilder.getHomeUrl());
urls.put("about", UrlBuilder.getUrl(Route.ABOUT));
urls.put("search", UrlBuilder.getUrl(Route.SEARCH));
urls.put("customsearch", UrlBuilder.getUrl(Route.CUSTOMSEARCH));
urls.put("extendedsearch", UrlBuilder.getUrl(Route.EXTENDED_SEARCH));
urls.put("termsOfUse", UrlBuilder.getUrl(Route.TERMS_OF_USE));
urls.put("login", UrlBuilder.getLoginUrl());
urls.put("logout", UrlBuilder.getLogoutUrl());

View file

@ -58,11 +58,11 @@ import edu.ucsf.vitro.opensocial.OpenSocialManager;
* Paged search controller that uses the search engine
*/
@WebServlet(name = "CustomSearchController", urlPatterns = {"/customsearch","/customsearch.jsp","/customfedsearch","/customsearchcontroller"} )
public class CustomSearchController extends FreemarkerHttpServlet {
@WebServlet(name = "ExtendedSearchController", urlPatterns = {"/extendedsearch","/extendedsearch.jsp","/extendedfedsearch","/extendedsearchcontroller"} )
public class ExtendedSearchController extends FreemarkerHttpServlet {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(CustomSearchController.class);
private static final Log log = LogFactory.getLog(ExtendedSearchController.class);
protected static final int DEFAULT_HITS_PER_PAGE = 25;
protected static final int DEFAULT_MAX_HIT_COUNT = 1000;
@ -74,6 +74,7 @@ public class CustomSearchController extends FreemarkerHttpServlet {
private static final String PARAM_CLASSGROUP = "classgroup";
private static final String PARAM_RDFTYPE = "type";
private static final String PARAM_QUERY_TEXT = "querytext";
private static final String EXTENDEDSEARCH = "/extendedsearch";
protected static final Map<Format,Map<Result,String>> templateTable;
@ -552,9 +553,9 @@ public class CustomSearchController extends FreemarkerHttpServlet {
}
public static class VClassGroupSearchLink extends LinkTemplateModel {
long count = 0;
long count = 0;
VClassGroupSearchLink(String querytext, VClassGroup classgroup, long count) {
super(classgroup.getPublicName(), "/customsearch", PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, classgroup.getURI());
super(classgroup.getPublicName(), EXTENDEDSEARCH, PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, classgroup.getURI());
this.count = count;
}
@ -564,7 +565,7 @@ public class CustomSearchController extends FreemarkerHttpServlet {
public static class VClassSearchLink extends LinkTemplateModel {
long count = 0;
VClassSearchLink(String querytext, VClass type, long count) {
super(type.getName(), "/customsearch", PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI());
super(type.getName(), EXTENDEDSEARCH, PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI());
this.count = count;
}
@ -738,15 +739,15 @@ public class CustomSearchController extends FreemarkerHttpServlet {
HashMap<Result,String> resultsToTemplates = new HashMap<Result,String>();
// set up HTML format
resultsToTemplates.put(Result.PAGED, "search-pagedResults.ftl");
resultsToTemplates.put(Result.ERROR, "search-error.ftl");
resultsToTemplates.put(Result.PAGED, "extendedsearch-pagedResults.ftl");
resultsToTemplates.put(Result.ERROR, "extendedsearch-error.ftl");
// resultsToTemplates.put(Result.BAD_QUERY, "search-badQuery.ftl");
table.put(Format.HTML, Collections.unmodifiableMap(resultsToTemplates));
// set up XML format
resultsToTemplates = new HashMap<Result,String>();
resultsToTemplates.put(Result.PAGED, "search-xmlResults.ftl");
resultsToTemplates.put(Result.ERROR, "search-xmlError.ftl");
resultsToTemplates.put(Result.PAGED, "extendedsearch-xmlResults.ftl");
resultsToTemplates.put(Result.ERROR, "extendedsearch-xmlError.ftl");
// resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl");
table.put(Format.XML, Collections.unmodifiableMap(resultsToTemplates));
@ -754,8 +755,8 @@ public class CustomSearchController extends FreemarkerHttpServlet {
// set up CSV format
resultsToTemplates = new HashMap<Result,String>();
resultsToTemplates.put(Result.PAGED, "search-csvResults.ftl");
resultsToTemplates.put(Result.ERROR, "search-csvError.ftl");
resultsToTemplates.put(Result.PAGED, "extendedsearch-csvResults.ftl");
resultsToTemplates.put(Result.ERROR, "extendedsearch-csvError.ftl");
// resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl");
table.put(Format.CSV, Collections.unmodifiableMap(resultsToTemplates));

View file

@ -0,0 +1,77 @@
/*!
* jQuery QueryBuilder 2.5.2
* Locale: Russian (ru)
* Licensed under MIT (https://opensource.org/licenses/MIT)
*/
(function(root, factory) {
if (typeof define == 'function' && define.amd) {
define(['jquery', 'query-builder'], factory);
}
else {
factory(root.jQuery);
}
}(this, function($) {
"use strict";
var QueryBuilder = $.fn.queryBuilder;
QueryBuilder.regional['ru'] = {
"__locale": "Russian (ru)",
"add_rule": "Добавить условие",
"add_group": "Добавить группу",
"delete_rule": "Удалить",
"delete_group": "Удалить",
"conditions": {
"AND": "И",
"OR": "ИЛИ"
},
"operators": {
"equal": "равно",
"not_equal": "не равно",
"in": "из указанных",
"not_in": "не из указанных",
"less": "меньше",
"less_or_equal": "меньше или равно",
"greater": "больше",
"greater_or_equal": "больше или равно",
"between": "между",
"begins_with": "начинается с",
"not_begins_with": "не начинается с",
"contains": "содержит",
"not_contains": "не содержит",
"ends_with": "оканчивается на",
"not_ends_with": "не оканчивается на",
"is_empty": "пустая строка",
"is_not_empty": "не пустая строка",
"is_null": "пусто",
"is_not_null": "не пусто"
},
"errors": {
"no_filter": "Фильтр не выбран",
"empty_group": "Группа пуста",
"radio_empty": "Не выбранно значение",
"checkbox_empty": "Не выбранно значение",
"select_empty": "Не выбранно значение",
"string_empty": "Не заполненно",
"string_exceed_min_length": "Должен содержать больше {0} символов",
"string_exceed_max_length": "Должен содержать меньше {0} символов",
"string_invalid_format": "Неверный формат ({0})",
"number_nan": "Не число",
"number_not_integer": "Не число",
"number_not_double": "Не число",
"number_exceed_min": "Должно быть больше {0}",
"number_exceed_max": "Должно быть меньше, чем {0}",
"number_wrong_step": "Должно быть кратно {0}",
"datetime_empty": "Не заполненно",
"datetime_invalid": "Неверный формат даты ({0})",
"datetime_exceed_min": "Должно быть, после {0}",
"datetime_exceed_max": "Должно быть, до {0}",
"boolean_not_valid": "Не логическое",
"operator_not_multiple": "Оператор \"{1}\" не поддерживает много значений"
},
"invert": "Инвертировать"
};
QueryBuilder.defaults({ lang_code: 'ru' });
}));

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
<#-- $This file is distributed under the terms of the license in LICENSE$ -->
<#-- Template for displaying search error message -->
<#include "queryBuilder.ftl">
<#if title??>
<div class="errorPageTitle">
<h2>${title?html}</h2>
</div>
</#if>
<div id="nomatchingTitle">
<p>
${message?html}
</p>
</div>
<#include "search-help.ftl" >

View file

@ -0,0 +1,251 @@
<#-- $This file is distributed under the terms of the license in LICENSE$ -->
<#-- Template for displaying paged search results -->
<#include "queryBuilder.ftl">
<h2 class="searchResultsHeader">
<#escape x as x?html>
<div id='searchQueryResults'> ${i18n().search_results_for} '${querytext}'</div>
<div id='limitedToClassGroup'> <#if classGroupName?has_content>${i18n().limited_to_type} '${classGroupName}'</#if> </div>
<div id='limitedToType'> <#if typeName?has_content>${i18n().limited_to_type} '${typeName}'</#if> </div>
</#escape>
<script type="text/javascript">
var url = window.location.toString();
if (url.indexOf("?") == -1){
var queryText = 'querytext=${querytext}';
} else {
var urlArray = url.split("?");
var queryText = urlArray[1];
}
var urlsBase = '${urls.base}';
</script>
<img id="downloadIcon" src="images/download-icon.png" alt="${i18n().download_results}" title="${i18n().download_results}" />
<#-- <span id="downloadResults" style="float:left"></span> -->
</h2>
<span id="searchHelp"><a href="${urls.base}/searchHelp" title="${i18n().search_help}">${i18n().not_expected_results}</a></span>
<div class="contentsBrowseGroup">
<#-- Refinement links -->
<#if classGroupLinks?has_content && classGroupLinks?size gt 1>
<div class="searchTOC">
<h4>${i18n().display_only}</h4>
<ul>
<#list classGroupLinks as link>
<li><a href="${link.url}" title="${i18n().class_group_link}">${link.text}</a><span>(${link.count})</span></li>
</#list>
</ul>
</div>
</#if>
<#if classLinks?has_content && classLinks?size gt 1 >
<div class="searchTOC">
<#if classGroupName?has_content>
<h4>${i18n().limit} ${classGroupName} ${i18n().to}</h4>
<#else>
<h4>${i18n().limit_to}</h4>
</#if>
<ul>
<#list classLinks as link>
<li><a href="${link.url}" title="${i18n().class_link}">${link.text}</a><span>(${link.count})</span></li>
</#list>
</ul>
</div>
</#if>
<div class="virtualArticleSwitch">
<label class="switch">${i18n().show_virtual_article}
<input id="virtualArticleCheck" type="checkbox" checked="false" onclick="showVirtualArticles();">
</label>
<#if user.loggedIn>
<button onclick="createNewCompilation()">${i18n().create_compilation_button}</button>
</#if>
</div>
<#-- Search results -->
<ul class="searchhits">
<#list individuals as individual>
<li>
<@shortView uri=individual.uri viewContext="search" />
</li>
</#list>
</ul>
<#-- Paging controls -->
<#if (pagingLinks?size > 0)>
<div class="searchpages">
${i18n().pages}:
<#if prevPage??><a class="prev" href="${prevPage}" title="${i18n().previous}">${i18n().previous}</a></#if>
<#list pagingLinks as link>
<#if link.url??>
<a href="${link.url}" title="${i18n().page_link}">${link.text}</a>
<#else>
<span>${link.text}</span> <#-- no link if current page -->
</#if>
</#list>
<#if nextPage??><a class="next" href="${nextPage}" title="${i18n().next_capitalized}">${i18n().next_capitalized}</a></#if>
</div>
</#if>
<br />
<#-- VIVO OpenSocial Extension by UCSF -->
<#if openSocial??>
<#if openSocial.visible>
<h3>OpenSocial</h3>
<script type="text/javascript" language="javascript">
// find the 'Search' gadget(s).
var searchGadgets = my.findGadgetsAttachingTo("gadgets-search");
var keyword = '${querytext}';
// add params to these gadgets
if (keyword) {
for (var i = 0; i < searchGadgets.length; i++) {
var searchGadget = searchGadgets[i];
searchGadget.additionalParams = searchGadget.additionalParams || {};
searchGadget.additionalParams["keyword"] = keyword;
}
}
else { // remove these gadgets
my.removeGadgets(searchGadgets);
}
</script>
<div id="gadgets-search" class="gadgets-gadget-parent" style="display:inline-block"></div>
</#if>
</#if>
</div> <!-- end contentsBrowseGroup -->
<script>
document.addEventListener('DOMContentLoaded', createVirtualCompilation(), false);
$('input[type=checkbox]').removeAttr('checked');
function showVirtualArticles(){
var checkBox = document.getElementById("virtualArticleCheck");
if (checkBox.checked == true){
$('.searchResult').hide();
$('.virtualArticlePart').show();
} else {
$('.searchResult').show();
$('.virtualArticlePart').hide();
}
}
function createNewCompilation() {
var excerpts = $('.compilationDraftExcerpt').toArray();
if (excerpts.length == 0){
alert("${i18n().create_compilation_no_excerpts}");
return;
}
var compilationName = window.prompt("${i18n().create_compilation_enter_name_notification}");
if (!compilationName){
alert("${i18n().create_compilation_no_name_entered_error}");
return;
}
var iframe = document.createElement("iframe");
var excerptsCounter = $('.compilationDraftExcerpt').length;
iframe.setAttribute("src", "${urls.base}/editRequestDispatch?typeOfNew=https%3A%2F%2Flitvinovg.pro%2Ftext_structures%23compilation&editForm=edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.CompilationGenerator&excerptsCount=" + excerptsCounter);
iframe.style.width = "1px";
iframe.style.height = "1px";
iframe.id="newCompilationIframe";
//iframe.style.display="none";
document.body.appendChild(iframe);
$('#newCompilationIframe').on('load', function(){
fillOutForm(compilationName);
});
}
function fillOutForm(compilationName){
var iframeDoc = document.getElementById('newCompilationIframe').contentWindow.document;
var rules = $('#builder').queryBuilder('getRules', { get_flags: true });
var query = format_query_string(rules, "", false);
iframeDoc.getElementById('queryBuilderRules').value = JSON.stringify(rules);
iframeDoc.getElementById('rawQueryString').value = query;
iframeDoc.getElementById('newCompilationLabel').value = compilationName;
var excerpts = $('.compilationDraftExcerpt').toArray();
for (i = 0;i < excerpts.length;i++){
var excerptUri = excerpts[i].getAttribute('partUri');
var excerptName = excerpts[i].getAttribute('partName');
var number = i + 1;
iframeDoc.getElementById("tocLevel" + number + "Name").value = excerptName + " (" + compilationName + ")";
iframeDoc.getElementById("tocItem" + number + "Name").value = excerptName + " (" + compilationName + ")";
iframeDoc.getElementById("excerpt" + number).value = excerptUri;
}
$('#newCompilationIframe').off('load');
iframeDoc.getElementById('submit').click();
$('#newCompilationIframe').on('load', function(){
redirectToNewCompilation();
});
}
function redirectToNewCompilation(){
var newURL = document.getElementById('newCompilationIframe').contentWindow.location.href;
window.open(newURL,"_self");
}
function createVirtualCompilation(){
let workSet = new Set();
let biblioSet = new Set();
var workDivs = $('.virtualArticleWork');
var biblioDivs = $('.virtualArticleBibliography');
biblioDivs.each(function() {
biblioSet.add($(this).html());
});
workDivs.each(function() {
workSet.add($(this).html());
});
var workArr = Array.from(workSet);
workArr.sort();
var biblioArr = Array.from(biblioSet);
biblioArr.sort();
if (workArr.length > 0 ) {
$('<div class="virtualWorks virtualArticlePart"><button type="button" style="border:none;width: 100%; text-align:left;" class="collapsible">${i18n().sources_excerpt_button_text}</button><div class="virtualWorks"></div></div>').insertAfter($('.virtualArticlePart').last());
for (let value of workArr){
$('.virtualWorks').last().append( '<div class="work"><p>' + value + '</p></div>' );
}
}
if (biblioArr.length > 0 ) {
$('<div class="virtualBibliography virtualArticlePart"><button type="button" style="border:none;width: 100%; text-align:left;" class="collapsible">${i18n().literature_excerpt_button_text}</button><div class="virtualBibliography"></div></div>').insertAfter($('.virtualArticlePart').last());
for (let value of biblioArr){
$('.virtualBibliography').last().append( '<div class="bibliography"><p>' + value + '</p></div>' );
}
}
$('.virtualWorks').hide();
$('.virtualBibliography').hide();
$('.virtualArticlePart').hide();
var coll = document.getElementsByClassName("collapsible");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
} else {
content.style.display = "block";
}
});
}
createRemoveButtons();
}
</script>
${stylesheets.add('<link rel="stylesheet" href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />',
'<link rel="stylesheet" href="${urls.base}/css/search.css" />',
'<link rel="stylesheet" type="text/css" href="${urls.base}/css/jquery_plugins/qtip/jquery.qtip.min.css" />')}
${headScripts.add('<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>',
'<script type="text/javascript" src="${urls.base}/js/jquery_plugins/qtip/jquery.qtip.min.js"></script>',
'<script type="text/javascript" src="${urls.base}/js/tiny_mce/tiny_mce.js"></script>'
)}
${scripts.add('<script type="text/javascript" src="${urls.base}/js/searchDownload.js"></script>')}

View file

@ -0,0 +1,279 @@
<div id="querybuilder-container">
<div id="CounterSearchWrapper">
<div id="builder">
<div id="SearchTitle">${i18n().extended_search_label}</div>
</div>
<div class="btn-group-bottom">
<div id="settingsButtons">
<div id="OuterWrapperButtons">
<div id="wrapperButtons">
<button id="btn-set" class="btn btn-success set-json" data-target="basic">${i18n().extended_search_example}</button>
<button id="btn-reset-button" class="btn btn-warning reset" data-target="basic">${i18n().extended_search_clean}</button>
<!-- <button id="btn-get" class="btn btn-primary parse-json" data-target="basic">Get rules</button> -->
<div id="SelectResults">
<label style="display:inline;" for="hitsPerPage">${i18n().extended_search_results_on_page}</label>
<@selectHitsPerPage/>
</div>
</div>
</div>
<button id="btn-search-expand" class="btn btn-warning reset" data-target="basic">${i18n().extended_search_execute_search}</button>
</div>
</div>
</div>
</div>
<script src= "js/search/query-builder.standalone.min.js"></script>
<script src= "js/search/query-builder.ru.js"></script>
<script>
var rules_example =
<#if searchFields?has_content>
{
"condition": "AND",
"rules": [
{
"id": "titles",
"field": "titles",
"type": "string",
"input": "text",
"operator": "not_contains",
"value": "постмодернизм"
},
{
"condition": "OR",
"rules": [
{
"id": "keywords",
"field": "keywords",
"type": "string",
"input": "text",
"operator": "contains",
"value": "постмодернизм"
},
{
"id": "bibliography",
"field": "bibliography",
"type": "string",
"input": "text",
"operator": "contains",
"value": "постмодернизм"
}
]
}
],
"valid": true
};
<#else>
{
"condition": "AND",
"rules": [
{
"id": "ALLTEXT",
"field": "everywhere",
"type": "string",
"input": "text",
"operator": "contains",
"value": "publication"
}
],
"valid": true
};
</#if>
var rules_start;
<#if queryBuilderRules??>
rules_start = ${queryBuilderRules};
<#else>
if (localStorage.getItem('queryBuilderFormSaved') === true || localStorage.getItem('queryBuilderFormSaved') === 'true') {
rules_start = JSON.parse(localStorage.getItem('queryBuilderForm'));
} else {
rules_start = rules_example;
}
</#if>
$('#builder').queryBuilder({
filters: [
<#if searchFields?has_content>
<#list searchFields as field>
<#if field.hasFilters == "true">
<@multivalueField field=field />
<#else>
<@freeField field=field />
</#if>
</#list>
<#else>
{
id: 'ALLTEXT',
label: 'Everywhere',
type: 'string',
operators: ['contains', 'not_contains']
},
</#if>
],
lang_code: 'ru',
rules: rules_start
});
$('#btn-reset-button').on('click', function() {
$('#builder').queryBuilder('reset');
});
$('#btn-set').on('click', function() {
$('#builder').queryBuilder('setRules', rules_example);
});
$('#btn-search-expand').on('click', function() {
var json_result = $('#builder').queryBuilder('getRules', { get_flags: true });
var query_string = format_query_string(json_result,"");
var hits = $("#hitsPerPageSelect :selected");
if (!$.isEmptyObject(query_string)) {
localStorage.setItem('queryBuilderForm',JSON.stringify(json_result));
localStorage.setItem('queryBuilderFormSaved',true);
var queryURL = "${urls.extendedsearch}?querytext=" + query_string;
if (hits !== null){
queryURL = queryURL.concat("&hitsPerPage=",hits.text());
}
window.open(queryURL,"_self")
}
//if (!$.isEmptyObject(json_result)) {
// alert(JSON.stringify(json_result, null, 2));
// alert(query_string);
//}
});
function format_query_string(json_query, string_query, htmlEncode = true){
if ('condition' in json_query && 'rules' in json_query ){
if (json_query.rules.length > 1 ) {
string_query +=" ( ";
var i;
for (i = 0; i < json_query.rules.length; i++) {
string_query = format_query_string(json_query.rules[i], string_query, htmlEncode);
if (i + 1 < json_query.rules.length){
string_query += " " + json_query.condition + " ";
}
}
string_query +=" ) ";
} else {
string_query = format_query_string(json_query.rules[0], string_query, htmlEncode);
}
} else if ( 'field' in json_query && 'value' in json_query ){
if ('operator' in json_query && json_query.operator.startsWith("not_")){
string_query += "NOT ";
}
if (json_query.hasOwnProperty('data') && json_query.data.hasOwnProperty('value')){
var value = "\"" + json_query.data.value.toString()+ "\"";
} else {
var value = json_query.value.toString();
}
if (htmlEncode){
string_query += json_query.field + ":" + value.replace(/[']+/g,'').replace(/#/g,'%23') ;
} else {
string_query += json_query.field + ":" + value.replace(/[']+/g,'');
}
}
return string_query;
}
function excludeDocByURI(name, uri){
var allRules = $('#builder').queryBuilder('getRules', { get_flags: true });
var excludeRuleString = '{ "id": "URI", "field": "URI", "type": "string", "flags":{}, "input": "text", "operator": "not_contains", "data":{} }';
var excludeRule = JSON.parse(excludeRuleString);
excludeRule.data.value = uri;
excludeRule.flags.value_readonly = true;
excludeRule.flags.filter_readonly = true;
excludeRule.flags.operator_readonly = true;
excludeRule.value = name;
if (allRules !== null){
if (allRules.condition === "AND"){
allRules.rules.push(excludeRule);
} else {
var outerRulesString = '{ "condition": "AND", "rules": [] }';
var outerRules = JSON.parse(outerRulesString);
outerRules.rules.push(allRules);
outerRules.rules.push(excludeRule);
allRules = outerRules;
}
$('#builder').queryBuilder('setRules', allRules);
var elements = document.querySelectorAll('[parturi="' + uri + '"]');
var i;
for (i = 0; i < elements.length; i++) {
elements[i].parentElement.remove();
}
}
}
function createRemoveButtons(){
$('.compilationDraftExcerpt').each(function(index){ createRemoveButton(this)});
}
function createRemoveButton(element){
var uri = element.getAttribute('parturi');
var button = element.querySelector('button');
var name = button.textContent;
var a = document.createElement('a');
a.setAttribute('href',"javascript:excludeDocByURI(\'" + escapeQutes(name) + "\',\'" + escapeQutes(uri) + "\');");
a.setAttribute('class',"removeDocFromSearch");
a.textContent = "${i18n().remove_doc_from_search_results}" ;
button.appendChild(a);
}
function escapeQutes(input) {
return input
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;")
}
</script>
<#macro freeField field >
{
id: '${field.field}',
label: '${field.name}',
type: 'string',
operators: ['contains', 'not_contains']
},
</#macro>
<#macro multivalueField field >
{
id: '${field.field}',
label: '${field.name}',
type: 'string',
input: 'select',
values: {
<#if searchFields??>
<#list searchFilters as filter>
<#if filter.field == field.field>
'&quot;${filter.id}&quot;':'${filter.name}',
</#if>
</#list>
<#else>
{
id: 'ALLTEXT',
label: 'Everywhere',
type: 'string',
operators: ['contains', 'not_contains']
},
</#if>
},
operators: ['contains', 'not_contains']
},
</#macro>
<#macro selectHitsPerPage>
<#if !hitsPerPage?? >
<#assign hitsPerPage = 20 >
</#if>
<#assign hitsValues= [20,40,60,80,100]>
<select name="hitsPerPage" id="hitsPerPageSelect">
<option value="${hitsPerPage}" selected="selected">${hitsPerPage}</option>
<#list hitsValues as hppValue>
<#if hppValue != hitsPerPage>
<option value="${hppValue}">${hppValue}</option>
</#if>
</#list>
</select>
</#macro>