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

View File

@ -17,8 +17,8 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@alfresco/adf-content-services": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c", "@alfresco/adf-content-services": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"@alfresco/adf-core": "2.3.0-9401e77e0cbb2037644786eb8f89a1b3dfc8843c", "@alfresco/adf-core": "2.3.0-5d7ccbeb9ad8713e6ae0ab5266c81da123501700",
"@angular/animations": "5.1.1", "@angular/animations": "5.1.1",
"@angular/cdk": "5.0.1", "@angular/cdk": "5.0.1",
"@angular/common": "5.1.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 { LayoutComponent } from './components/layout/layout.component';
import { HeaderComponent } from './components/header/header.component'; import { HeaderComponent } from './components/header/header.component';
import { CurrentUserComponent } from './components/current-user/current-user.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 { SidenavComponent } from './components/sidenav/sidenav.component';
import { AboutComponent } from './components/about/about.component'; import { AboutComponent } from './components/about/about.component';
import { LocationLinkComponent } from './components/location-link/location-link.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 { ContentManagementService } from './common/services/content-management.service';
import { NodeActionsService } from './common/services/node-actions.service'; import { NodeActionsService } from './common/services/node-actions.service';
import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInputModule } from '@angular/material'; import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInputModule } from '@angular/material';
import { SearchComponent } from './components/search/search.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -90,7 +91,7 @@ import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInpu
LayoutComponent, LayoutComponent,
HeaderComponent, HeaderComponent,
CurrentUserComponent, CurrentUserComponent,
SearchComponent, SearchInputComponent,
SidenavComponent, SidenavComponent,
FilesComponent, FilesComponent,
FavoritesComponent, FavoritesComponent,
@ -110,7 +111,8 @@ import { MatMenuModule, MatIconModule, MatButtonModule, MatDialogModule, MatInpu
NodeUnshareDirective, NodeUnshareDirective,
NodeInfoDirective, NodeInfoDirective,
NodeVersionsDirective, NodeVersionsDirective,
VersionManagerDialogAdapterComponent VersionManagerDialogAdapterComponent,
SearchComponent
], ],
providers: [ providers: [
{ {

View File

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

View File

@ -11,7 +11,7 @@
</a> </a>
</adf-toolbar-title> </adf-toolbar-title>
<app-search></app-search> <app-search-input></app-search-input>
<adf-toolbar-divider></adf-toolbar-divider> <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 <adf-search
[highlight]="true" #search
(optionClicked)="onItemClicked($event)"> [searchTerm]="searchedWord"
</adf-search-control> [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 .adf-search-results {
:host { @include flex-row;
overflow: hidden;
}
adf-search-control { &__facets {
color: $alfresco-white; 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 @@
/*! import { async, ComponentFixture, TestBed } from '@angular/core/testing';
* @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 { SearchComponent } from './search.component'; import { SearchComponent } from './search.component';
describe('SearchComponent', () => { describe('SearchComponent', () => {
let fixture; let component: SearchComponent;
let component; let fixture: ComponentFixture<SearchComponent>;
let router: Router;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ declarations: [ SearchComponent ]
RouterTestingModule
],
declarations: [
SearchComponent
],
schemas: [ NO_ERRORS_SCHEMA ]
}) })
.compileComponents() .compileComponents();
.then(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
router = TestBed.get(Router);
fixture.detectChanges();
});
})); }));
describe('onItemClicked()', () => { beforeEach(() => {
it('opens preview if node is file', () => { fixture = TestBed.createComponent(SearchComponent);
spyOn(router, 'navigate').and.stub(); component = fixture.componentInstance;
const node = { entry: { isFile: true, id: 'node-id', parentId: 'parent-id' } }; fixture.detectChanges();
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', () => { xit('should create', () => {
const node = { entry: { isFolder: true } }; expect(component).toBeTruthy();
spyOn(router, 'navigate');
component.onItemClicked(node);
expect(router.navigate).toHaveBeenCalled();
});
}); });
}); });

View File

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