mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ADF-5419][aca] support multiple search form (#2173)
* [ADF-5419][aca] support multiple search form * * revert app config and tests added * * removed unused key * * resolved conflicts and upgrade adf * * fix e2e * * update defalut name
This commit is contained in:
@@ -150,13 +150,13 @@ describe('Empty list views', () => {
|
|||||||
expect(await pagination.isNextButtonPresent()).toBe(false, 'Next button is present');
|
expect(await pagination.isNextButtonPresent()).toBe(false, 'Next button is present');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('[C279189] Search filters panel is not displayed on empty Search Results page', async () => {
|
it('[C279189] Search filters panel is displayed on empty Search Results page', async () => {
|
||||||
await searchInput.clickSearchButton();
|
await searchInput.clickSearchButton();
|
||||||
/* cspell:disable-next-line */
|
/* cspell:disable-next-line */
|
||||||
await searchInput.searchFor('qwertyuiop');
|
await searchInput.searchFor('qwertyuiop');
|
||||||
await dataTable.waitForBody();
|
await dataTable.waitForBody();
|
||||||
|
|
||||||
expect(await searchResultsPage.filters.isSearchFiltersPanelDisplayed()).toBe(false, 'Search filters panel is present');
|
expect(await searchResultsPage.filters.isSearchFiltersPanelDisplayed()).toBe(true, 'Search filters panel is not present');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('[C290020] Empty Search results - Libraries', async () => {
|
it('[C290020] Empty Search results - Libraries', async () => {
|
||||||
|
@@ -189,7 +189,7 @@
|
|||||||
"multi-value-pipe-separator": ", ",
|
"multi-value-pipe-separator": ", ",
|
||||||
"multi-value-chips": true
|
"multi-value-chips": true
|
||||||
},
|
},
|
||||||
"search": {
|
"search": [{
|
||||||
"filterWithContains": true,
|
"filterWithContains": true,
|
||||||
"aca:fields": ["cm:name", "cm:title", "cm:description", "TEXT", "TAG"],
|
"aca:fields": ["cm:name", "cm:title", "cm:description", "TEXT", "TAG"],
|
||||||
"include": ["path", "allowableOperations", "properties"],
|
"include": ["path", "allowableOperations", "properties"],
|
||||||
@@ -372,8 +372,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
},
|
"name": "APP.BROWSE.SEARCH.DEFAULT_SEARCH",
|
||||||
|
"default": true
|
||||||
|
}],
|
||||||
"search-headers": {
|
"search-headers": {
|
||||||
"filterWithContains": true,
|
"filterWithContains": true,
|
||||||
"app:fields": [
|
"app:fields": [
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
#searchFilter
|
#searchFilter
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'adf-search-filter--hidden':
|
'adf-search-filter--hidden':
|
||||||
hideSearchFilter() || !(showFacetFilter$ | async)
|
!(showFacetFilter$ | async)
|
||||||
}"
|
}"
|
||||||
></adf-search-filter>
|
></adf-search-filter>
|
||||||
<div class="adf-search-results__content">
|
<div class="adf-search-results__content">
|
||||||
@@ -25,10 +25,8 @@
|
|||||||
mode="indeterminate"
|
mode="indeterminate"
|
||||||
>
|
>
|
||||||
</mat-progress-bar>
|
</mat-progress-bar>
|
||||||
<div
|
<div class="adf-search-results__content-header content">
|
||||||
class="adf-search-results__content-header content"
|
<adf-search-form (formChange)="onFormChange($event)"></adf-search-form>
|
||||||
*ngIf="data?.list.entries.length"
|
|
||||||
>
|
|
||||||
<div class="content__side--left">
|
<div class="content__side--left">
|
||||||
<div
|
<div
|
||||||
class="adf-search-results--info-text"
|
class="adf-search-results--info-text"
|
||||||
|
@@ -81,6 +81,8 @@
|
|||||||
&__side--left {
|
&__side--left {
|
||||||
@include flex-column;
|
@include flex-column;
|
||||||
height: unset;
|
height: unset;
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,17 +23,18 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { SearchResultsComponent } from './search-results.component';
|
import { SearchResultsComponent } from './search-results.component';
|
||||||
import { AppTestingModule } from '../../../testing/app-testing.module';
|
import { AppTestingModule } from '../../../testing/app-testing.module';
|
||||||
import { AppSearchResultsModule } from '../search-results.module';
|
import { AppSearchResultsModule } from '../search-results.module';
|
||||||
import { CoreModule, AppConfigService, AlfrescoApiService, TranslationService } from '@alfresco/adf-core';
|
import { AlfrescoApiService, AppConfigService, CoreModule, TranslationService } from '@alfresco/adf-core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { NavigateToFolder, SnackbarErrorAction } from '@alfresco/aca-shared/store';
|
import { NavigateToFolder, SnackbarErrorAction } from '@alfresco/aca-shared/store';
|
||||||
import { Pagination, SearchRequest } from '@alfresco/js-api';
|
import { Pagination, SearchRequest } from '@alfresco/js-api';
|
||||||
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
import { SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
describe('SearchComponent', () => {
|
describe('SearchComponent', () => {
|
||||||
let component: SearchResultsComponent;
|
let component: SearchResultsComponent;
|
||||||
@@ -45,8 +46,10 @@ describe('SearchComponent', () => {
|
|||||||
let translate: TranslationService;
|
let translate: TranslationService;
|
||||||
let router: Router;
|
let router: Router;
|
||||||
const searchRequest = {} as SearchRequest;
|
const searchRequest = {} as SearchRequest;
|
||||||
|
let params: BehaviorSubject<any>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
params = new BehaviorSubject({ q: 'TYPE: "cm:folder" AND %28=cm: name: email OR cm: name: budget%29' });
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), CoreModule.forRoot(), AppTestingModule, AppSearchResultsModule],
|
imports: [TranslateModule.forRoot(), CoreModule.forRoot(), AppTestingModule, AppSearchResultsModule],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -58,11 +61,7 @@ describe('SearchComponent', () => {
|
|||||||
sortingPreferenceKey: ''
|
sortingPreferenceKey: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
params: [
|
params: params.asObservable()
|
||||||
{
|
|
||||||
q: 'TYPE: "cm:folder" AND %28=cm: name: email OR cm: name: budget%29'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -75,6 +74,10 @@ describe('SearchComponent', () => {
|
|||||||
translate = TestBed.inject(TranslationService);
|
translate = TestBed.inject(TranslationService);
|
||||||
router = TestBed.inject(Router);
|
router = TestBed.inject(Router);
|
||||||
|
|
||||||
|
config.config = {
|
||||||
|
search: {}
|
||||||
|
};
|
||||||
|
|
||||||
fixture = TestBed.createComponent(SearchResultsComponent);
|
fixture = TestBed.createComponent(SearchResultsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
||||||
@@ -83,6 +86,10 @@ describe('SearchComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
params.complete();
|
||||||
|
});
|
||||||
|
|
||||||
it('should raise an error if search fails', fakeAsync(() => {
|
it('should raise an error if search fails', fakeAsync(() => {
|
||||||
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(
|
spyOn(alfrescoApi.searchApi, 'search').and.returnValue(
|
||||||
Promise.reject({
|
Promise.reject({
|
||||||
@@ -165,47 +172,23 @@ describe('SearchComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should format user input according to the configuration fields', () => {
|
it('should format user input according to the configuration fields', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('hello', ['cm:name', 'cm:title']);
|
||||||
search: {
|
|
||||||
'aca:fields': ['cm:name', 'cm:title']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('hello');
|
|
||||||
expect(query).toBe(`(cm:name:"hello*" OR cm:title:"hello*")`);
|
expect(query).toBe(`(cm:name:"hello*" OR cm:title:"hello*")`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format user input as cm:name if configuration not provided', () => {
|
it('should format user input as cm:name if configuration not provided', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('hello', undefined);
|
||||||
search: {
|
|
||||||
'aca:fields': undefined
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('hello');
|
|
||||||
expect(query).toBe(`(cm:name:"hello*")`);
|
expect(query).toBe(`(cm:name:"hello*")`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use AND operator when conjunction has no operators', () => {
|
it('should use AND operator when conjunction has no operators', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('big yellow banana', ['cm:name']);
|
||||||
search: {
|
|
||||||
'aca:fields': ['cm:name']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('big yellow banana');
|
|
||||||
|
|
||||||
expect(query).toBe(`(cm:name:"big*") AND (cm:name:"yellow*") AND (cm:name:"banana*")`);
|
expect(query).toBe(`(cm:name:"big*") AND (cm:name:"yellow*") AND (cm:name:"banana*")`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support conjunctions with AND operator', () => {
|
it('should support conjunctions with AND operator', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('big AND yellow AND banana', ['cm:name', 'cm:title']);
|
||||||
search: {
|
|
||||||
'aca:fields': ['cm:name', 'cm:title']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('big AND yellow AND banana');
|
|
||||||
|
|
||||||
expect(query).toBe(
|
expect(query).toBe(
|
||||||
`(cm:name:"big*" OR cm:title:"big*") AND (cm:name:"yellow*" OR cm:title:"yellow*") AND (cm:name:"banana*" OR cm:title:"banana*")`
|
`(cm:name:"big*" OR cm:title:"big*") AND (cm:name:"yellow*" OR cm:title:"yellow*") AND (cm:name:"banana*" OR cm:title:"banana*")`
|
||||||
@@ -213,13 +196,7 @@ describe('SearchComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should support conjunctions with OR operator', () => {
|
it('should support conjunctions with OR operator', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('big OR yellow OR banana', ['cm:name', 'cm:title']);
|
||||||
search: {
|
|
||||||
'aca:fields': ['cm:name', 'cm:title']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('big OR yellow OR banana');
|
|
||||||
|
|
||||||
expect(query).toBe(
|
expect(query).toBe(
|
||||||
`(cm:name:"big*" OR cm:title:"big*") OR (cm:name:"yellow*" OR cm:title:"yellow*") OR (cm:name:"banana*" OR cm:title:"banana*")`
|
`(cm:name:"big*" OR cm:title:"big*") OR (cm:name:"yellow*" OR cm:title:"yellow*") OR (cm:name:"banana*" OR cm:title:"banana*")`
|
||||||
@@ -227,25 +204,13 @@ describe('SearchComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should support exact term matching with default fields', () => {
|
it('should support exact term matching with default fields', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('=orange', ['cm:name', 'cm:title']);
|
||||||
search: {
|
|
||||||
'aca:fields': ['cm:name', 'cm:title']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('=orange');
|
|
||||||
|
|
||||||
expect(query).toBe(`(=cm:name:"orange" OR =cm:title:"orange")`);
|
expect(query).toBe(`(=cm:name:"orange" OR =cm:title:"orange")`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support exact term matching with operators', () => {
|
it('should support exact term matching with operators', () => {
|
||||||
config.config = {
|
const query = component.formatSearchQuery('=test1.pdf or =test2.pdf', ['cm:name', 'cm:title']);
|
||||||
search: {
|
|
||||||
'aca:fields': ['cm:name', 'cm:title']
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const query = component.formatSearchQuery('=test1.pdf or =test2.pdf');
|
|
||||||
|
|
||||||
expect(query).toBe(`(=cm:name:"test1.pdf" OR =cm:title:"test1.pdf") or (=cm:name:"test2.pdf" OR =cm:title:"test2.pdf")`);
|
expect(query).toBe(`(=cm:name:"test1.pdf" OR =cm:title:"test1.pdf") or (=cm:name:"test2.pdf" OR =cm:title:"test2.pdf")`);
|
||||||
});
|
});
|
||||||
@@ -294,4 +259,17 @@ describe('SearchComponent', () => {
|
|||||||
});
|
});
|
||||||
expect(queryBuilder.update).toHaveBeenCalled();
|
expect(queryBuilder.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update the user query whenever param changed', () => {
|
||||||
|
params.next({ q: '=orange' });
|
||||||
|
expect(queryBuilder.userQuery).toBe(`((=cm:name:"orange"))`);
|
||||||
|
expect(queryBuilder.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the user query whenever configuration changed', () => {
|
||||||
|
params.next({ q: '=orange' });
|
||||||
|
queryBuilder.configUpdated.next({ 'aca:fields': ['cm:tag'] } as any);
|
||||||
|
expect(queryBuilder.userQuery).toBe(`((=cm:tag:"orange"))`);
|
||||||
|
expect(queryBuilder.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -24,25 +24,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { Pagination, MinimalNodeEntity, ResultSetPaging } from '@alfresco/js-api';
|
import { MinimalNodeEntity, Pagination, ResultSetPaging } from '@alfresco/js-api';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
import { SearchQueryBuilderService, SearchFilterComponent } from '@alfresco/adf-content-services';
|
import { SearchFilterComponent, SearchForm, SearchQueryBuilderService } from '@alfresco/adf-content-services';
|
||||||
import { PageComponent } from '../../page.component';
|
import { PageComponent } from '../../page.component';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
AppStore,
|
AppStore,
|
||||||
NavigateToFolder,
|
|
||||||
SnackbarErrorAction,
|
|
||||||
showFacetFilter,
|
|
||||||
infoDrawerPreview,
|
infoDrawerPreview,
|
||||||
ShowInfoDrawerPreviewAction,
|
NavigateToFolder,
|
||||||
|
SetInfoDrawerPreviewStateAction,
|
||||||
SetInfoDrawerStateAction,
|
SetInfoDrawerStateAction,
|
||||||
SetInfoDrawerPreviewStateAction
|
showFacetFilter,
|
||||||
|
ShowInfoDrawerPreviewAction,
|
||||||
|
SnackbarErrorAction
|
||||||
} from '@alfresco/aca-shared/store';
|
} from '@alfresco/aca-shared/store';
|
||||||
import { ContentManagementService } from '../../../services/content-management.service';
|
import { ContentManagementService } from '../../../services/content-management.service';
|
||||||
import { AppConfigService, TranslationService } from '@alfresco/adf-core';
|
import { TranslationService } from '@alfresco/adf-core';
|
||||||
import { Observable } from 'rxjs';
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'aca-search-results',
|
selector: 'aca-search-results',
|
||||||
@@ -68,7 +69,6 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private queryBuilder: SearchQueryBuilderService,
|
private queryBuilder: SearchQueryBuilderService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private config: AppConfigService,
|
|
||||||
store: Store<AppStore>,
|
store: Store<AppStore>,
|
||||||
extensions: AppExtensionService,
|
extensions: AppExtensionService,
|
||||||
content: ContentManagementService,
|
content: ContentManagementService,
|
||||||
@@ -84,11 +84,21 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
|
|
||||||
this.showFacetFilter$ = store.select(showFacetFilter);
|
this.showFacetFilter$ = store.select(showFacetFilter);
|
||||||
this.infoDrawerPreview$ = store.select(infoDrawerPreview);
|
this.infoDrawerPreview$ = store.select(infoDrawerPreview);
|
||||||
|
combineLatest([this.route.params, this.queryBuilder.configUpdated])
|
||||||
|
.pipe(takeUntil(this.onDestroy$))
|
||||||
|
.subscribe(([params, searchConfig]) => {
|
||||||
|
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
||||||
|
const query = this.formatSearchQuery(this.searchedWord, searchConfig['aca:fields']);
|
||||||
|
if (query) {
|
||||||
|
this.queryBuilder.userQuery = decodeURIComponent(query);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
|
||||||
|
this.queryBuilder.resetToDefaults();
|
||||||
this.sorting = this.getSorting();
|
this.sorting = this.getSorting();
|
||||||
|
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
@@ -114,10 +124,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
if (this.route) {
|
if (this.route) {
|
||||||
this.route.params.forEach((params: Params) => {
|
this.route.params.forEach((params: Params) => {
|
||||||
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
||||||
const query = this.formatSearchQuery(this.searchedWord);
|
if (this.searchedWord) {
|
||||||
|
|
||||||
if (query) {
|
|
||||||
this.queryBuilder.userQuery = decodeURIComponent(query);
|
|
||||||
this.queryBuilder.update();
|
this.queryBuilder.update();
|
||||||
} else {
|
} else {
|
||||||
this.queryBuilder.userQuery = null;
|
this.queryBuilder.userQuery = null;
|
||||||
@@ -165,7 +172,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
return '(' + fields.map((field) => `${prefix}${field}:"${term}${suffix}"`).join(' OR ') + ')';
|
return '(' + fields.map((field) => `${prefix}${field}:"${term}${suffix}"`).join(' OR ') + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
formatSearchQuery(userInput: string) {
|
formatSearchQuery(userInput: string, fields = ['cm:name']) {
|
||||||
if (!userInput) {
|
if (!userInput) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -176,7 +183,6 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
return userInput;
|
return userInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = this.config.get<string[]>('search.aca:fields', ['cm:name']);
|
|
||||||
const words = userInput.split(' ');
|
const words = userInput.split(' ');
|
||||||
|
|
||||||
if (words.length > 1) {
|
if (words.length > 1) {
|
||||||
@@ -247,8 +253,8 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hideSearchFilter() {
|
onFormChange(form: SearchForm) {
|
||||||
return !this.totalResults && !this.hasSelectedFilters;
|
this.queryBuilder.updateSelectedConfiguration(form.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPreviewClosed() {
|
onPreviewClosed() {
|
||||||
|
@@ -167,7 +167,8 @@
|
|||||||
"TITLE": "About"
|
"TITLE": "About"
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"TITLE": "Search Results",
|
"DEFAULT_SEARCH": "Default",
|
||||||
|
"TITLE": "Search Results",
|
||||||
"FOUND_RESULTS": "{{ number }} results found",
|
"FOUND_RESULTS": "{{ number }} results found",
|
||||||
"FOUND_ONE_RESULT": "{{ number }} result found",
|
"FOUND_ONE_RESULT": "{{ number }} result found",
|
||||||
"CUSTOM_ROW": {
|
"CUSTOM_ROW": {
|
||||||
|
Reference in New Issue
Block a user