[ACA-1086] search container (#271)

* rename search to search-input

* search results placeholder

* initial search filter integration

* layout

* upgrade to latest ADF alpha to get pagination fixes

* set default page size for search results to 25

* disable test until full search implementation
This commit is contained in:
Denys Vuika 2018-04-03 15:26:49 +01:00 committed by Cilibiu Bogdan
parent 22894089df
commit b63f6c0197
15 changed files with 406 additions and 109 deletions

14
package-lock.json generated
View File

@ -5,11 +5,11 @@
"requires": true,
"dependencies": {
"@alfresco/adf-content-services": {
"version": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c",
"resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c.tgz",
"integrity": "sha512-Ocq5zCvMDvDGzyYuUdazFneCSi0YSzUUkmfg5cktHp21xHuSED9yNnBv5tnCYGn+RsTK+FGro4IZ8WIAMWkaqw==",
"version": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700.tgz",
"integrity": "sha512-dlH5TAp+tml1Ohbgj6QYOCjTgfDul88M5+4TDjzbxfGmh+02AarknqunttKcxlbraky+RaimwpNDG7l8CqztHA==",
"requires": {
"@alfresco/adf-core": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c",
"@alfresco/adf-core": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",
"@angular/common": "5.1.1",
@ -61,9 +61,9 @@
}
},
"@alfresco/adf-core": {
"version": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c.tgz",
"integrity": "sha512-cTz++U8lBnxPXYOi03vWJVWL5Dyv4JtkN+Vzw4GxHsPoctq6nUje9IHYitkGg0V4/rbJKU3Isvwr6MfuLeQnFA==",
"version": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700.tgz",
"integrity": "sha512-r+F90SMT6RGRvSJccjIhyvmhUCNaWMG+pUQeZYxj3XDuX7+iZsDZ3GNJ+MeBl3rX8F5r4m+GwjT1/yL/zMSaOA==",
"requires": {
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",

View File

@ -17,8 +17,8 @@
},
"private": true,
"dependencies": {
"@alfresco/adf-content-services": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c",
"@alfresco/adf-core": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c",
"@alfresco/adf-content-services": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"@alfresco/adf-core": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1",
"@angular/common": "5.1.1",

View File

@ -154,5 +154,117 @@
}
]
}
},
"search": {
"limits": {
"permissionEvaluationTime": null,
"permissionEvaluationCount": null
},
"filterQueries": [
{ "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
{ "query": "NOT cm:creator:System" }
],
"facetFields": {
"facets": [
{ "field": "content.mimetype", "mincount": 1, "label": "Type" },
{ "field": "content.size", "mincount": 1, "label": "Size" },
{ "field": "creator", "mincount": 1, "label": "Creator" },
{ "field": "modifier", "mincount": 1, "label": "Modifier" }
]
},
"facetQueries": [
{ "query": "created:2018", "label": "Created This Year" },
{ "query": "content.mimetype", "label": "Type" },
{ "query": "content.size:[0 TO 10240]", "label": "Size: xtra small"},
{ "query": "content.size:[10240 TO 102400]", "label": "Size: small"},
{ "query": "content.size:[102400 TO 1048576]", "label": "Size: medium" },
{ "query": "content.size:[1048576 TO 16777216]", "label": "Size: large" },
{ "query": "content.size:[16777216 TO 134217728]", "label": "Size: xtra large" },
{ "query": "content.size:[134217728 TO MAX]", "label": "Size: XX large" }
],
"query": {
"categories": [
{
"id": "broken",
"name": "Broken Facet",
"enabled": false,
"expanded": false,
"component": {
"selector": "adf-search-text",
"settings": {
"field": "fieldname"
}
}
},
{
"id": "queryName",
"name": "Name",
"enabled": true,
"expanded": true,
"component": {
"selector": "adf-search-text",
"settings": {
"pattern": "cm:name:'(.*?)'",
"field": "cm:name",
"placeholder": "Enter the name"
}
}
},
{
"id": "queryFields",
"name": "Fields",
"enabled": true,
"expanded": false,
"component": {
"selector": "adf-search-fields",
"settings": {
"field": null,
"options": [
{ "name": "Name", "value": "name", "fields": ["name"], "default": true },
{ "name": "File Size", "value": "content.sizeInBytes", "fields": ["content"], "default": true },
{ "name": "Modified On", "value": "modifiedAt", "fields": ["modifiedAt"], "default": true },
{ "name": "Modified By", "value": "modifiedByUser.displayName", "fields": ["modifiedByUser"], "default": true }
]
}
}
},
{
"id": "queryType",
"name": "Type",
"enabled": true,
"expanded": false,
"component": {
"selector": "adf-search-radio",
"settings": {
"field": null,
"options": [
{ "name": "None", "value": "", "default": true },
{ "name": "All", "value": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
{ "name": "Folder", "value": "TYPE:'cm:folder'" },
{ "name": "Document", "value": "TYPE:'cm:content'" }
]
}
}
},
{
"id": "queryLocations",
"name": "Locations",
"enabled": true,
"expanded": false,
"component": {
"selector": "adf-search-scope-locations",
"settings": {
"field": null,
"options": [
{ "name": "Default", "value": "nodes", "default": true },
{ "name": "Nodes", "value": "nodes" },
{ "name": "Deleted Nodes", "value": "deleted-nodes" },
{ "name": "Versions", "value": "versions" }
]
}
}
}
]
}
}
}

View File

@ -46,7 +46,7 @@ import { TrashcanComponent } from './components/trashcan/trashcan.component';
import { LayoutComponent } from './components/layout/layout.component';
import { HeaderComponent } from './components/header/header.component';
import { CurrentUserComponent } from './components/current-user/current-user.component';
import { SearchComponent } from './components/search/search.component';
import { SearchInputComponent } from './components/search-input/search-input.component';
import { SidenavComponent } from './components/sidenav/sidenav.component';
import { AboutComponent } from './components/about/about.component';
import { LocationLinkComponent } from './components/location-link/location-link.component';
@ -64,6 +64,7 @@ import { BrowsingFilesService } from './common/services/browsing-files.service';
import { ContentManagementService } from './common/services/content-management.service';
import { NodeActionsService } from './common/services/node-actions.service';
import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInputModule } from '@angular/material';
import { SearchComponent } from './components/search/search.component';
@NgModule({
imports: [
@ -90,7 +91,7 @@ import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInpu
LayoutComponent,
HeaderComponent,
CurrentUserComponent,
SearchComponent,
SearchInputComponent,
SidenavComponent,
FilesComponent,
FavoritesComponent,
@ -110,7 +111,8 @@ import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInpu
NodeUnshareDirective,
NodeInfoDirective,
NodeVersionsDirective,
VersionManagerDialogAdapterComponent
VersionManagerDialogAdapterComponent,
SearchComponent
],
providers: [
{

View File

@ -39,6 +39,7 @@ import { AboutComponent } from './components/about/about.component';
import { LoginComponent } from './components/login/login.component';
import { PreviewComponent } from './components/preview/preview.component';
import { GenericErrorComponent } from './components/generic-error/generic-error.component';
import { SearchComponent } from './components/search/search.component';
export const APP_ROUTES: Routes = [
{
@ -215,6 +216,10 @@ export const APP_ROUTES: Routes = [
i18nTitle: 'APP.BROWSE.ABOUT.TITLE'
}
},
{
path: 'search',
component: SearchComponent
},
{
path: '**',
component: GenericErrorComponent

View File

@ -11,7 +11,7 @@
</a>
</adf-toolbar-title>
<app-search></app-search>
<app-search-input></app-search-input>
<adf-toolbar-divider></adf-toolbar-divider>

View File

@ -0,0 +1,5 @@
<adf-search-control
[highlight]="true"
(optionClicked)="onItemClicked($event)"
(submit)="onSearchSubmit($event)">
</adf-search-control>

View File

@ -0,0 +1,10 @@
@import 'variables';
// todo: remove once ADF 2.0 is out
:host {
overflow: hidden;
}
adf-search-control {
color: $alfresco-white;
}

View File

@ -0,0 +1,78 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { SearchInputComponent } from './search-input.component';
describe('SearchInputComponent', () => {
let fixture;
let component;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
SearchInputComponent
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(SearchInputComponent);
component = fixture.componentInstance;
router = TestBed.get(Router);
fixture.detectChanges();
});
}));
describe('onItemClicked()', () => {
it('opens preview if node is file', () => {
spyOn(router, 'navigate').and.stub();
const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } };
component.onItemClicked(node);
expect(router.navigate['calls'].argsFor(0)[0])
.toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]);
});
it('navigates if node is folder', () => {
const node = { entry: { isFolder: true } };
spyOn(router, 'navigate');
component.onItemClicked(node);
expect(router.navigate).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,62 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { MinimalNodeEntity } from 'alfresco-js-api';
@Component({
selector: 'app-search-input',
templateUrl: 'search-input.component.html',
styleUrls: ['search-input.component.scss']
})
export class SearchInputComponent {
constructor(
private router: Router) {
}
onItemClicked(node: MinimalNodeEntity) {
if (node && node.entry) {
if (node.entry.isFile) {
this.router.navigate([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]);
} else if (node.entry.isFolder) {
this.router.navigate([ '/personal-files', node.entry.id ]);
}
}
}
/**
* Called when the user submits the search, e.g. hits enter or clicks submit
*
* @param event Parameters relating to the search
*/
onSearchSubmit(event: KeyboardEvent) {
const value = (event.target as HTMLInputElement).value;
this.router.navigate(['/search', {
q: value
}]);
}
}

View File

@ -1,4 +1,32 @@
<adf-search-control
[highlight]="true"
(optionClicked)="onItemClicked($event)">
</adf-search-control>
<adf-search
#search
[searchTerm]="searchedWord"
[maxResults]="maxItems"
[skipResults]="skipCount"
(resultLoaded)="onSearchResultLoaded($event)">
</adf-search>
<div class="adf-search-results__facets">
<adf-search-chip-list
[searchFilter]="searchFilter">
</adf-search-chip-list>
</div>
<div class="adf-search-results">
<adf-search-filter
#searchFilter>
</adf-search-filter>
<div class="adf-search-results__content">
<adf-document-list
#documentList
[attr.class]="documentList.isEmpty() ? 'adf-document-list--empty' : ''"
[node]="data">
</adf-document-list>
<adf-pagination *ngIf="!documentList.isEmpty()"
[target]="documentList"
(changePageSize)="onRefreshPagination($event)">
</adf-pagination>
</div>
</div>

View File

@ -1,10 +1,26 @@
@import 'variables';
@import 'mixins';
// todo: remove once ADF 2.0 is out
:host {
overflow: hidden;
}
.adf-search-results {
@include flex-row;
adf-search-control {
color: $alfresco-white;
&__facets {
display: flex;
height: 35px;
flex-direction: column;
justify-content: center;
padding: 5px;
border-bottom: 1px solid #eee;
}
&__content {
@include flex-column;
border-left: 1px solid #eee;
}
.adf-search-filter {
min-width: 260px;
padding: 5px;
height: 100%;
overflow: scroll;
}
}

View File

@ -1,78 +1,25 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2018 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TestBed, async } from '@angular/core/testing';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchComponent } from './search.component';
describe('SearchComponent', () => {
let fixture;
let component;
let router: Router;
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
SearchComponent
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
router = TestBed.get(Router);
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SearchComponent ]
})
.compileComponents();
}));
fixture.detectChanges();
});
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
describe('onItemClicked()', () => {
it('opens preview if node is file', () => {
spyOn(router, 'navigate').and.stub();
const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } };
component.onItemClicked(node);
expect(router.navigate['calls'].argsFor(0)[0])
.toEqual([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]);
});
it('navigates if node is folder', () => {
const node = { entry: { isFolder: true } };
spyOn(router, 'navigate');
component.onItemClicked(node);
expect(router.navigate).toHaveBeenCalled();
});
});
xit('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -23,28 +23,59 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { Component, OnInit, Optional, ViewChild } from '@angular/core';
import { NodePaging, Pagination } from 'alfresco-js-api';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { SearchQueryBuilderService, SearchComponent as AdfSearchComponent } from '@alfresco/adf-content-services';
@Component({
selector: 'app-search',
templateUrl: 'search.component.html',
styleUrls: ['search.component.scss']
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent {
export class SearchComponent implements OnInit {
@ViewChild('search')
search: AdfSearchComponent;
queryParamName = 'q';
searchedWord = '';
data: NodePaging;
maxItems = 5;
skipCount = 0;
constructor(
private router: Router) {
public router: Router,
private queryBuilder: SearchQueryBuilderService,
@Optional() private route: ActivatedRoute) {
queryBuilder.paging = {
skipCount: 0,
maxItems: 25
};
}
onItemClicked(node: MinimalNodeEntity) {
if (node && node.entry) {
if (node.entry.isFile) {
this.router.navigate([`/personal-files/${node.entry.parentId}/preview/`, node.entry.id]);
} else if (node.entry.isFolder) {
this.router.navigate([ '/personal-files', node.entry.id ]);
}
ngOnInit() {
if (this.route) {
this.route.params.forEach((params: Params) => {
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
this.queryBuilder.queryFragments['queryName'] = `cm:name:'${this.searchedWord}'`;
this.queryBuilder.update();
});
}
}
onSearchResultLoaded(nodePaging: NodePaging) {
this.data = nodePaging;
}
onRefreshPagination(pagination: Pagination) {
this.maxItems = pagination.maxItems;
this.skipCount = pagination.skipCount;
this.queryBuilder.paging = {
maxItems: pagination.maxItems,
skipCount: pagination.skipCount
};
this.queryBuilder.update();
}
}

View File

@ -13,6 +13,7 @@ html, body {
app-root,
app-layout,
adf-layout-container,
app-search,
ng-component {
@include flex-column;
}