-
Notifications
You must be signed in to change notification settings - Fork 269
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat(content-services): simple search input component for applications * feat(content-services): support i18n for search input * feat(content-services): apply review suggestions * feat(content-services): apply review suggestions * chore: fix husky install
- Loading branch information
1 parent
adec3e6
commit aab03cc
Showing
14 changed files
with
342 additions
and
9 deletions.
There are no files selected for viewing
70 changes: 70 additions & 0 deletions
70
docs/content-services/components/search-input.component.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Search Input | ||
|
||
`Component`, `Standalone` | ||
|
||
A minimalistic search input component that formats user query according to the provided fields. | ||
|
||
```html | ||
<app-search-input | ||
[fields]="['cm:name']" | ||
(changed)="onSearchQueryChanged($event)"> | ||
</app-search-input> | ||
``` | ||
|
||
> Notes: this component does not perform search operations. | ||
> It handles the user input, formats and produces the search query to use with `Search Query Builder` or other services. | ||
## Properties | ||
|
||
- `fields` **string[]** - optional, a list of fields to use in the formatted search query, defaults to `[cm:name]` | ||
- `value` **string** - optional, initial input value | ||
- `label` **string** - optional, display label | ||
- `placeholder` **string** - optional, display placeholder | ||
|
||
## Events | ||
|
||
- `changed` **EventEmitter\<string\>**: emits when user presses `Enter` or moves the focus out of the input area | ||
|
||
## Examples | ||
|
||
```html | ||
<app-search-input | ||
[fields]="['cm:name', 'cm:title', 'cm:description', 'TEXT', 'TAG']" | ||
(changed)="onSearchQueryChanged($event)"> | ||
</app-search-input> | ||
``` | ||
|
||
In the example above, the search is performed against the following fields: | ||
`cm:name`, `cm:title`, `cm:description`, `TEXT` and `TAG`. | ||
|
||
The Search Input is going to produce the following results for user inputs: | ||
|
||
user types `test` | ||
|
||
```text | ||
(cm:name:"test*" OR cm:title:"test*" OR cm:description:"test*" OR TEXT:"test*" OR TAG:"test*") | ||
``` | ||
|
||
user types `*` | ||
|
||
```text | ||
(cm:name:"**" OR cm:title:"**" OR cm:description:"**" OR TEXT:"**" OR TAG:"**") | ||
``` | ||
|
||
user types `one two` | ||
|
||
```text | ||
(cm:name:"one*" OR cm:title:"one*" OR cm:description:"one*" OR TEXT:"one*" OR TAG:"one*") AND (cm:name:"two*" OR cm:title:"two*" OR cm:description:"two*" OR TEXT:"two*" OR TAG:"two*") | ||
``` | ||
|
||
user types `one AND two` | ||
|
||
```text | ||
(cm:name:"one*" OR cm:title:"one*" OR cm:description:"one*" OR TEXT:"one*" OR TAG:"one*") AND (cm:name:"two*" OR cm:title:"two*" OR cm:description:"two*" OR TEXT:"two*" OR TAG:"two*") | ||
``` | ||
|
||
user types `one OR two` | ||
|
||
```text | ||
(cm:name:"one*" OR cm:title:"one*" OR cm:description:"one*" OR TEXT:"one*" OR TAG:"one*") OR (cm:name:"two*" OR cm:title:"two*" OR cm:description:"two*" OR TEXT:"two*" OR TAG:"two*") | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.storybook | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
lib/content-services/src/lib/search/components/search-input/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/*! | ||
* @license | ||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
export * from './search-input.component'; |
6 changes: 6 additions & 0 deletions
6
lib/content-services/src/lib/search/components/search-input/search-input.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<div class="adf-search-input-container"> | ||
<mat-form-field appearance="outline"> | ||
<mat-label *ngIf="label">{{ label | translate }}</mat-label> | ||
<input matInput [placeholder]="placeholder | translate" [value]="value" (change)="onSearchInputChanged($event)" /> | ||
</mat-form-field> | ||
</div> |
12 changes: 12 additions & 0 deletions
12
lib/content-services/src/lib/search/components/search-input/search-input.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* stylelint-disable selector-class-pattern */ | ||
.adf-search-input-container { | ||
margin: 10px; | ||
|
||
mat-form-field { | ||
width: 100%; | ||
|
||
.mat-form-field-wrapper { | ||
padding: 0; | ||
} | ||
} | ||
} |
113 changes: 113 additions & 0 deletions
113
lib/content-services/src/lib/search/components/search-input/search-input.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/*! | ||
* @license | ||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
import { MatInputHarness } from '@angular/material/input/testing'; | ||
import { SearchInputComponent } from '@alfresco/adf-content-services'; | ||
import { HarnessLoader } from '@angular/cdk/testing'; | ||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
import { ContentTestingModule } from '../../../testing/content.testing.module'; | ||
|
||
describe('SearchInputComponent', () => { | ||
let loader: HarnessLoader; | ||
let component: SearchInputComponent; | ||
let fixture: ComponentFixture<SearchInputComponent>; | ||
|
||
/** | ||
* Sets the search input value | ||
* | ||
* @param value the value to set | ||
*/ | ||
async function setInputValue(value: string) { | ||
const input = await loader.getHarness(MatInputHarness); | ||
await input.setValue(value); | ||
await (await input.host()).dispatchEvent('change'); | ||
|
||
fixture.detectChanges(); | ||
await fixture.whenStable(); | ||
} | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [TranslateModule.forRoot(), ContentTestingModule, SearchInputComponent] | ||
}); | ||
|
||
fixture = TestBed.createComponent(SearchInputComponent); | ||
component = fixture.componentInstance; | ||
|
||
loader = TestbedHarnessEnvironment.loader(fixture); | ||
}); | ||
|
||
it('should show custom placeholder', async () => { | ||
component.placeholder = 'custom placeholder'; | ||
|
||
const input = await loader.getHarness(MatInputHarness); | ||
const placeholder = await input.getPlaceholder(); | ||
expect(placeholder).toBe('custom placeholder'); | ||
}); | ||
|
||
it('should use multiple fields', async () => { | ||
component.fields = ['cm:description', 'TAG']; | ||
|
||
fixture.detectChanges(); | ||
await fixture.whenStable(); | ||
|
||
let formatted = ''; | ||
component.changed.subscribe((val) => (formatted = val)); | ||
|
||
await setInputValue('test'); | ||
|
||
expect(formatted).toBe('(cm:description:"test*" OR TAG:"test*")'); | ||
}); | ||
|
||
it('should emit changed event with [cm:name]', async () => { | ||
let formatted = ''; | ||
component.changed.subscribe((val) => (formatted = val)); | ||
|
||
await setInputValue('test'); | ||
|
||
expect(formatted).toBe('(cm:name:"test*")'); | ||
}); | ||
|
||
it('should format with AND by default', async () => { | ||
let formatted = ''; | ||
component.changed.subscribe((val) => (formatted = val)); | ||
|
||
await setInputValue('one two'); | ||
|
||
expect(formatted).toBe('(cm:name:"one*") AND (cm:name:"two*")'); | ||
}); | ||
|
||
it('should format with OR if specified directly', async () => { | ||
let formatted = ''; | ||
component.changed.subscribe((val) => (formatted = val)); | ||
|
||
await setInputValue('one OR two'); | ||
|
||
expect(formatted).toBe('(cm:name:"one*") OR (cm:name:"two*")'); | ||
}); | ||
|
||
it('should format with AND if specified directly', async () => { | ||
let formatted = ''; | ||
component.changed.subscribe((val) => (formatted = val)); | ||
|
||
await setInputValue('one AND two'); | ||
|
||
expect(formatted).toBe('(cm:name:"one*") AND (cm:name:"two*")'); | ||
}); | ||
}); |
105 changes: 105 additions & 0 deletions
105
lib/content-services/src/lib/search/components/search-input/search-input.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/*! | ||
* @license | ||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { CommonModule } from '@angular/common'; | ||
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; | ||
import { MatFormFieldModule } from '@angular/material/form-field'; | ||
import { MatInputModule } from '@angular/material/input'; | ||
import { TranslateModule } from '@ngx-translate/core'; | ||
|
||
@Component({ | ||
selector: 'app-search-input', | ||
standalone: true, | ||
imports: [CommonModule, MatFormFieldModule, MatInputModule, TranslateModule], | ||
templateUrl: `./search-input.component.html`, | ||
styleUrls: ['./search-input.component.scss'], | ||
encapsulation: ViewEncapsulation.None | ||
}) | ||
export class SearchInputComponent { | ||
@Input() | ||
value = ''; | ||
|
||
@Input() | ||
label = 'SEARCH.INPUT.LABEL'; | ||
|
||
@Input() | ||
placeholder = 'SEARCH.INPUT.PLACEHOLDER'; | ||
|
||
@Input() | ||
fields = ['cm:name']; | ||
|
||
@Output() | ||
changed = new EventEmitter<string>(); | ||
|
||
onSearchInputChanged(event: Event) { | ||
const input = event.target as HTMLInputElement; | ||
const searchTerm = input.value; | ||
|
||
const query = this.formatSearchQuery(searchTerm, this.fields); | ||
if (query) { | ||
this.changed.emit(decodeURIComponent(query)); | ||
} | ||
} | ||
|
||
private formatSearchQuery(userInput: string, fields = ['cm:name']): string { | ||
if (!userInput) { | ||
return null; | ||
} | ||
|
||
if (/^https?:\/\//.test(userInput)) { | ||
return this.formatFields(fields, userInput); | ||
} | ||
|
||
userInput = userInput.trim(); | ||
|
||
if (userInput.includes(':') || userInput.includes('"')) { | ||
return userInput; | ||
} | ||
|
||
const words = userInput.split(' '); | ||
|
||
if (words.length > 1) { | ||
const separator = words.some(this.isOperator) ? ' ' : ' AND '; | ||
return words.map((term) => (this.isOperator(term) ? term : this.formatFields(fields, term))).join(separator); | ||
} | ||
|
||
return this.formatFields(fields, userInput); | ||
} | ||
|
||
private isOperator(input: string): boolean { | ||
if (input) { | ||
input = input.trim().toUpperCase(); | ||
|
||
const operators = ['AND', 'OR']; | ||
return operators.includes(input); | ||
} | ||
return false; | ||
} | ||
|
||
private formatFields(fields: string[], term: string): string { | ||
let prefix = ''; | ||
let suffix = '*'; | ||
|
||
if (term.startsWith('=')) { | ||
prefix = '='; | ||
suffix = ''; | ||
term = term.substring(1); | ||
} | ||
|
||
return '(' + fields.map((field) => `${prefix}${field}:"${term}${suffix}"`).join(' OR ') + ')'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.storybook |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.storybook |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.