**: emits when user presses `Enter` or moves the focus out of the input area
+
+## Examples
+
+```html
+
+
+```
+
+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*")
+```
diff --git a/lib/content-services/.eslintignore b/lib/content-services/.eslintignore
new file mode 100644
index 0000000000..16523ff94f
--- /dev/null
+++ b/lib/content-services/.eslintignore
@@ -0,0 +1,2 @@
+.storybook
+coverage
diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json
index d0272e1e39..bc7b075e26 100644
--- a/lib/content-services/src/lib/i18n/en.json
+++ b/lib/content-services/src/lib/i18n/en.json
@@ -278,6 +278,8 @@
"ARIA-LABEL": "Search button"
},
"INPUT": {
+ "LABEL": "Search",
+ "PLACEHOLDER": "Search Query",
"ARIA-LABEL": "Search input"
},
"RESULTS": {
diff --git a/lib/content-services/src/lib/search/components/index.ts b/lib/content-services/src/lib/search/components/index.ts
index b38c8ab48c..434f20bca1 100644
--- a/lib/content-services/src/lib/search/components/index.ts
+++ b/lib/content-services/src/lib/search/components/index.ts
@@ -27,6 +27,7 @@ export * from './search-filter-chips';
export * from './search-filter-container';
export * from './search-filter-tabbed';
export * from './search-form';
+export * from './search-input';
export * from './search-logical-filter';
export * from './search-number-range';
export * from './search-panel';
diff --git a/lib/content-services/src/lib/search/components/search-input/index.ts b/lib/content-services/src/lib/search/components/search-input/index.ts
new file mode 100644
index 0000000000..2a3afd0bb7
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-input/index.ts
@@ -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';
diff --git a/lib/content-services/src/lib/search/components/search-input/search-input.component.html b/lib/content-services/src/lib/search/components/search-input/search-input.component.html
new file mode 100644
index 0000000000..2adba70603
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-input/search-input.component.html
@@ -0,0 +1,6 @@
+
+
+ {{ label | translate }}
+
+
+
diff --git a/lib/content-services/src/lib/search/components/search-input/search-input.component.scss b/lib/content-services/src/lib/search/components/search-input/search-input.component.scss
new file mode 100644
index 0000000000..20a12e11be
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-input/search-input.component.scss
@@ -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;
+ }
+ }
+}
diff --git a/lib/content-services/src/lib/search/components/search-input/search-input.component.spec.ts b/lib/content-services/src/lib/search/components/search-input/search-input.component.spec.ts
new file mode 100644
index 0000000000..28ad56f71a
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-input/search-input.component.spec.ts
@@ -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;
+
+ /**
+ * 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*")');
+ });
+});
diff --git a/lib/content-services/src/lib/search/components/search-input/search-input.component.ts b/lib/content-services/src/lib/search/components/search-input/search-input.component.ts
new file mode 100644
index 0000000000..7b14865e09
--- /dev/null
+++ b/lib/content-services/src/lib/search/components/search-input/search-input.component.ts
@@ -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();
+
+ 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 ') + ')';
+ }
+}
diff --git a/lib/content-services/src/lib/search/search.module.ts b/lib/content-services/src/lib/search/search.module.ts
index c8ca2c22a0..de295666ba 100644
--- a/lib/content-services/src/lib/search/search.module.ts
+++ b/lib/content-services/src/lib/search/search.module.ts
@@ -58,6 +58,7 @@ import { SearchDateRangeTabbedComponent } from './components/search-date-range-t
import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive';
import { SearchFacetChipTabbedComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
import { SearchFacetTabbedContentComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-tabbed-content.component';
+import { SearchInputComponent } from './components/search-input';
@NgModule({
imports: [
@@ -67,7 +68,8 @@ import { SearchFacetTabbedContentComponent } from './components/search-filter-ch
ReactiveFormsModule,
MaterialModule,
CoreModule,
- SearchTextModule
+ SearchTextModule,
+ SearchInputComponent
],
declarations: [
SearchComponent,
diff --git a/lib/core/.eslintignore b/lib/core/.eslintignore
new file mode 100644
index 0000000000..abb75b0d35
--- /dev/null
+++ b/lib/core/.eslintignore
@@ -0,0 +1 @@
+.storybook
diff --git a/lib/process-services-cloud/.eslintignore b/lib/process-services-cloud/.eslintignore
new file mode 100644
index 0000000000..abb75b0d35
--- /dev/null
+++ b/lib/process-services-cloud/.eslintignore
@@ -0,0 +1 @@
+.storybook
diff --git a/package-lock.json b/package-lock.json
index a7f9cbbcb2..19a7591842 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,7 +7,6 @@
"": {
"name": "alfresco-ng2-components",
"version": "6.7.1",
- "hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@angular/animations": "14.1.3",
@@ -34311,8 +34310,9 @@
},
"node_modules/husky": {
"version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
+ "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
"dev": true,
- "license": "MIT",
"bin": {
"husky": "lib/bin.js"
},
diff --git a/package.json b/package.json
index 61fee99b16..91d0c9d038 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"version": "6.7.1",
"author": "Hyland Software, Inc. and its affiliates",
"scripts": {
- "postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2015 browser module main",
+ "prepare": "husky install",
"ng": "nx",
"00": "echo -------------------------------------------- DOC -----------------------------------------------",
"build-doc-tools": "tsc -p ./tools/doc/tsconfig.json",
@@ -99,17 +99,13 @@
"@angular-eslint/template-parser": "16.2.0",
"@angular/cli": "~14.2.12",
"@angular/compiler-cli": "14.1.3",
- "@editorjs/editorjs": "^2.29.0",
"@editorjs/code": "2.9.0",
+ "@editorjs/editorjs": "^2.29.0",
"@editorjs/header": "2.8.1",
"@editorjs/inline-code": "1.5.0",
"@editorjs/list": "1.9.0",
"@editorjs/marker": "1.4.0",
"@editorjs/underline": "1.1.0",
- "editorjs-text-color-plugin": "2.0.4",
- "editorjs-html": "3.4.3",
- "editorjs-paragraph-with-alignment": "3.0.0",
- "@quanzo/change-font-size": "1.0.0",
"@nrwl/angular": "14.8.9",
"@nrwl/cli": "14.8.9",
"@nrwl/eslint-plugin-nx": "14.5.4",
@@ -118,6 +114,7 @@
"@nrwl/workspace": "14.8.9",
"@paperist/types-remark": "0.1.3",
"@playwright/test": "^1.35.1",
+ "@quanzo/change-font-size": "1.0.0",
"@storybook/addon-essentials": "6.5.10",
"@storybook/angular": "6.5.16",
"@storybook/builder-webpack5": "6.5.10",
@@ -141,6 +138,9 @@
"commander": "6.2.1",
"css-loader": "^6.10.0",
"dotenv": "16.1.3",
+ "editorjs-html": "3.4.3",
+ "editorjs-paragraph-with-alignment": "3.0.0",
+ "editorjs-text-color-plugin": "2.0.4",
"ejs": "^3.1.9",
"eslint": "^8.47.0",
"eslint-config-prettier": "^8.10.0",