diff --git a/e2e/protractor/suites/search/search-filters.test.ts b/e2e/protractor/suites/search/search-filters.test.ts
index ba1028ba6c..74cb928219 100644
--- a/e2e/protractor/suites/search/search-filters.test.ts
+++ b/e2e/protractor/suites/search/search-filters.test.ts
@@ -513,7 +513,7 @@ describe('Search filters', () => {
await peopleFilter.closeDialog();
await searchInput.clickSearchButton();
- await searchInput.searchFor(fileJpgUser1.name);
+ await searchInput.searchFor(`${fileJpgUser1.name}*`);
await dataTable.waitForBody();
const expectedUsers2 = [`${user1} ${user1}`];
diff --git a/projects/aca-content/assets/app.extensions.json b/projects/aca-content/assets/app.extensions.json
index 410cdb2e07..57f3a3e797 100644
--- a/projects/aca-content/assets/app.extensions.json
+++ b/projects/aca-content/assets/app.extensions.json
@@ -1532,7 +1532,27 @@
"visible": "app.areCategoriesEnabled"
}
}
- ]
+ ],
+ "highlight": {
+ "prefix": "",
+ "postfix": "",
+ "fields": [
+ {
+ "field": "cm:title"
+ },
+ {
+ "field": "cm:name"
+ },
+ {
+ "field": "cm:description",
+ "snippetCount": 1
+ },
+ {
+ "field": "cm:content",
+ "snippetCount": 1
+ }
+ ]
+ }
},
{
"id": "app.search.dublin-core",
@@ -1688,7 +1708,27 @@
}
}
}
- ]
+ ],
+ "highlight": {
+ "prefix": "",
+ "postfix": "",
+ "fields": [
+ {
+ "field": "cm:title"
+ },
+ {
+ "field": "cm:name"
+ },
+ {
+ "field": "cm:description",
+ "snippetCount": 1
+ },
+ {
+ "field": "cm:content",
+ "snippetCount": 1
+ }
+ ]
+ }
},
{
"id": "app.search.effectivity",
@@ -1846,7 +1886,27 @@
}
}
}
- ]
+ ],
+ "highlight": {
+ "prefix": "",
+ "postfix": "",
+ "fields": [
+ {
+ "field": "cm:title"
+ },
+ {
+ "field": "cm:name"
+ },
+ {
+ "field": "cm:description",
+ "snippetCount": 1
+ },
+ {
+ "field": "cm:content",
+ "snippetCount": 1
+ }
+ ]
+ }
}
],
diff --git a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.html b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.html
index 13d97c9b04..7358cd2ad5 100644
--- a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.html
+++ b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.html
@@ -1,12 +1,38 @@
-
- {{ name$ | async }}
-
-
- {{ name$ | async }}
-
- {{ title$ | async }}
+
+
+
+
+
diff --git a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.scss b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.scss
index 4e4a351053..603480cb7c 100644
--- a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.scss
+++ b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.scss
@@ -1,9 +1,26 @@
.aca-search-results-row {
+ padding: 10px 0;
+ width: inherit;
+
+ .aca-highlight {
+ background: var(--theme-search-highlight-background-color);
+ }
+
+ .aca-crop-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
.aca-result-location {
height: 15px;
padding-top: 3px;
}
+ .aca-result-content {
+ padding: 0 5px;
+ font-style: italic;
+ }
+
.aca-link {
text-decoration: none;
color: var(--theme-text-bold-color);
diff --git a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.spec.ts b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.spec.ts
new file mode 100644
index 0000000000..fa0bbf7c13
--- /dev/null
+++ b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.spec.ts
@@ -0,0 +1,123 @@
+/*!
+ * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Alfresco Example Content Application
+ *
+ * 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
+ * from Hyland Software. If not, see .
+ */
+
+import { NodeEntry, ResultSetRowEntry } from '@alfresco/js-api';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { first } from 'rxjs/operators';
+import { AppTestingModule } from '../../../testing/app-testing.module';
+import { SearchResultsRowComponent } from './search-results-row.component';
+
+describe('SearchResultsRowComponent', () => {
+ let component: SearchResultsRowComponent;
+ let fixture: ComponentFixture;
+
+ const nodeEntry: NodeEntry = {
+ entry: {
+ id: 'fake-node-entry',
+ modifiedByUser: { displayName: 'IChangeThings' },
+ modifiedAt: new Date(),
+ isFile: true,
+ properties: { 'cm:title': 'BananaRama' }
+ }
+ } as NodeEntry;
+
+ const resultEntry: ResultSetRowEntry = {
+ entry: {
+ id: 'fake-node-entry',
+ modifiedAt: new Date(),
+ isFile: true,
+ name: 'Random name',
+ properties: { 'cm:title': 'Random title', 'cm:description': 'some random description' },
+ search: {
+ score: 10,
+ highlight: [
+ {
+ field: 'cm:content',
+ snippets: [`Interesting random content`]
+ },
+ {
+ field: 'cm:name',
+ snippets: [`Random`]
+ },
+ {
+ field: 'cm:title',
+ snippets: [`Random title`]
+ },
+ {
+ field: 'cm:description',
+ snippets: [`some random description`]
+ }
+ ]
+ }
+ }
+ } as ResultSetRowEntry;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [AppTestingModule, SearchResultsRowComponent]
+ });
+
+ fixture = TestBed.createComponent(SearchResultsRowComponent);
+ component = fixture.componentInstance;
+ });
+
+ it('should show the current node', () => {
+ component.context = { row: { node: nodeEntry } };
+ fixture.detectChanges();
+
+ const element = fixture.nativeElement.querySelector('div');
+ expect(element).not.toBeNull();
+ });
+
+ it('should correctly parse highlights', (done) => {
+ component.context = { row: { node: resultEntry } };
+ component.content$
+ .asObservable()
+ .pipe(first())
+ .subscribe(() => {
+ fixture.detectChanges();
+
+ const nameElement: HTMLSpanElement = fixture.debugElement.query(By.css('.aca-link.aca-crop-text')).nativeElement;
+ expect(nameElement.innerHTML).toBe('Random');
+ expect(nameElement.title).toBe('Random');
+
+ const titleElement: HTMLSpanElement = fixture.debugElement.query(By.css('[data-automation-id="search-results-entry-title"]')).nativeElement;
+ expect(titleElement.innerHTML).toBe(' ( Random title )');
+ expect(titleElement.title).toBe('Random title');
+
+ const descriptionElement: HTMLDivElement = fixture.debugElement.query(
+ By.css('[data-automation-id="search-results-entry-description"]')
+ ).nativeElement;
+ expect(descriptionElement.innerHTML).toBe('some random description');
+ expect(descriptionElement.title).toBe('some random description');
+
+ const contentElement: HTMLDivElement = fixture.debugElement.query(By.css('.aca-result-content.aca-crop-text')).nativeElement;
+ expect(contentElement.innerHTML).toBe('...Interesting random content...');
+ expect(contentElement.title).toBe('...Interesting random content...');
+ done();
+ });
+ fixture.detectChanges();
+ });
+});
diff --git a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.ts b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.ts
index 2bb23bd140..941aaa1698 100644
--- a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.ts
+++ b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.component.ts
@@ -23,7 +23,7 @@
*/
import { Component, Input, OnInit, ViewEncapsulation, ChangeDetectionStrategy, OnDestroy } from '@angular/core';
-import { NodeEntry } from '@alfresco/js-api';
+import { NodeEntry, SearchEntryHighlight } from '@alfresco/js-api';
import { ViewNodeAction, NavigateToFolder } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store';
import { BehaviorSubject, Subject } from 'rxjs';
@@ -46,6 +46,9 @@ import { MatDialogModule } from '@angular/material/dialog';
host: { class: 'aca-search-results-row' }
})
export class SearchResultsRowComponent implements OnInit, OnDestroy {
+ private readonly highlightPrefix = "";
+ private readonly highlightPostfix = '';
+
private node: NodeEntry;
private onDestroy$ = new Subject();
@@ -54,6 +57,12 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
name$ = new BehaviorSubject('');
title$ = new BehaviorSubject('');
+ description$ = new BehaviorSubject('');
+ content$ = new BehaviorSubject('');
+ nameStripped = '';
+ titleStripped = '';
+ descriptionStripped = '';
+ contentStripped = '';
isFile = false;
@@ -86,13 +95,42 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
this.node = this.context.row.node;
this.isFile = this.node.entry.isFile;
- const { name, properties } = this.node.entry;
- const title = properties ? properties['cm:title'] : '';
-
+ const highlights: SearchEntryHighlight[] = this.node.entry['search']?.['highlight'];
+ let name = this.node.entry.name;
+ const properties = this.node.entry.properties;
+ let title = properties?.['cm:title'] || '';
+ let description = properties?.['cm:description'] || '';
+ let content = '';
+
+ highlights?.forEach((highlight) => {
+ switch (highlight.field) {
+ case 'cm:name':
+ name = highlight.snippets[0];
+ break;
+ case 'cm:title':
+ title = highlight.snippets[0];
+ break;
+ case 'cm:description':
+ description = highlight.snippets[0];
+ break;
+ case 'cm:content':
+ content = `...${highlight.snippets[0]}...`;
+ break;
+ default:
+ break;
+ }
+ });
this.name$.next(name);
+ this.description$.next(description);
+ this.content$.next(content);
+
+ this.nameStripped = this.stripHighlighting(name);
+ this.descriptionStripped = this.stripHighlighting(description);
+ this.contentStripped = this.stripHighlighting(content);
if (title !== name) {
- this.title$.next(title ? `( ${title} )` : '');
+ this.title$.next(title ? ` ( ${title} )` : '');
+ this.titleStripped = this.stripHighlighting(title);
}
}
@@ -114,4 +152,10 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
event.stopPropagation();
this.store.dispatch(new NavigateToFolder(this.node));
}
+
+ private stripHighlighting(highlightedContent: string): string {
+ return highlightedContent
+ ? highlightedContent.replace(new RegExp(this.highlightPrefix, 'g'), '').replace(new RegExp(this.highlightPostfix, 'g'), '')
+ : '';
+ }
}
diff --git a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.components.spec.ts b/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.components.spec.ts
deleted file mode 100644
index fc08ee3c51..0000000000
--- a/projects/aca-content/src/lib/components/search/search-results-row/search-results-row.components.spec.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*!
- * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
- *
- * Alfresco Example Content Application
- *
- * 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
- * from Hyland Software. If not, see .
- */
-
-import { NodeEntry } from '@alfresco/js-api';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { AppTestingModule } from '../../../testing/app-testing.module';
-import { SearchResultsRowComponent } from './search-results-row.component';
-
-describe('SearchResultsRowComponent', () => {
- let component: SearchResultsRowComponent;
- let fixture: ComponentFixture;
- const nodeEntry: NodeEntry = {
- entry: {
- id: 'fake-node-entry',
- modifiedByUser: { displayName: 'IChangeThings' },
- modifiedAt: new Date(),
- isFile: true,
- properties: { 'cm:title': 'BananaRama' }
- }
- } as NodeEntry;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [AppTestingModule, SearchResultsRowComponent]
- });
-
- fixture = TestBed.createComponent(SearchResultsRowComponent);
- component = fixture.componentInstance;
- });
-
- it('should show the current node', () => {
- component.context = { row: { node: nodeEntry } };
- fixture.detectChanges();
-
- const element = fixture.nativeElement.querySelector('div');
- expect(element).not.toBeNull();
- });
-});
diff --git a/projects/aca-content/src/lib/components/search/search-results/search-results.component.html b/projects/aca-content/src/lib/components/search/search-results/search-results.component.html
index 78d6505d5e..8990e8d73f 100644
--- a/projects/aca-content/src/lib/components/search/search-results/search-results.component.html
+++ b/projects/aca-content/src/lib/components/search/search-results/search-results.component.html
@@ -64,14 +64,6 @@
-
-
-
- {{context.row?.node?.entry?.properties && context.row?.node?.entry?.properties['cm:description']}}
-
-
-
-
diff --git a/projects/aca-content/src/lib/components/search/search-results/search-results.component.spec.ts b/projects/aca-content/src/lib/components/search/search-results/search-results.component.spec.ts
index ce3c18fbbb..eb2786ed40 100644
--- a/projects/aca-content/src/lib/components/search/search-results/search-results.component.spec.ts
+++ b/projects/aca-content/src/lib/components/search/search-results/search-results.component.spec.ts
@@ -178,6 +178,11 @@ describe('SearchComponent', () => {
expect(query).toBe(`(cm:name:"hello*" OR cm:title:"hello*")`);
});
+ it('should not apply suffix to the TEXT field for correct highlighting', () => {
+ const query = component.formatSearchQuery('hello', ['cm:name', 'TEXT']);
+ expect(query).toBe(`(cm:name:"hello*" OR TEXT:"hello")`);
+ });
+
it('should format user input as cm:name if configuration not provided', () => {
const query = component.formatSearchQuery('hello');
expect(query).toBe(`(cm:name:"hello*")`);
diff --git a/projects/aca-content/src/lib/components/search/search-results/search-results.component.ts b/projects/aca-content/src/lib/components/search/search-results/search-results.component.ts
index e8f516904d..31f43bfa11 100644
--- a/projects/aca-content/src/lib/components/search/search-results/search-results.component.ts
+++ b/projects/aca-content/src/lib/components/search/search-results/search-results.component.ts
@@ -206,7 +206,15 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
term = term.substring(1);
}
- return '(' + fields.map((field) => `${prefix}${field}:"${term}${suffix}"`).join(' OR ') + ')';
+ return (
+ '(' +
+ fields
+ .map((field) => {
+ return field !== 'TEXT' ? `${prefix}${field}:"${term}${suffix}"` : `${prefix}${field}:"${term}"`;
+ })
+ .join(' OR ') +
+ ')'
+ );
}
formatSearchQuery(userInput: string, fields = ['cm:name']) {
diff --git a/projects/aca-content/src/lib/ui/variables/variables.scss b/projects/aca-content/src/lib/ui/variables/variables.scss
index 5aca473ee9..aa2e77f543 100644
--- a/projects/aca-content/src/lib/ui/variables/variables.scss
+++ b/projects/aca-content/src/lib/ui/variables/variables.scss
@@ -41,6 +41,7 @@ $page-layout-header-background-color: $background-card-color;
$search-chip-icon-color: #757575;
$disabled-chip-background-color: #f5f5f5;
$contrast-gray: mat.get-color-from-palette($foreground, 'secondary-tex');
+$search-highlight-background-color: #ffd180;
// CSS Variables
$defaults: (
@@ -86,7 +87,8 @@ $defaults: (
--theme-page-layout-header-background-color: $page-layout-header-background-color,
--theme-search-chip-icon-color: $search-chip-icon-color,
--theme-disabled-chip-background-color: $disabled-chip-background-color,
- --theme-secondary-text: $secondary-text
+ --theme-secondary-text: $secondary-text,
+ --theme-search-highlight-background-color: $search-highlight-background-color
);
// propagates SCSS variables into the CSS variables scope
diff --git a/projects/aca-playwright-shared/src/page-objects/components/dataTable/data-table.component.ts b/projects/aca-playwright-shared/src/page-objects/components/dataTable/data-table.component.ts
index 48ca515aa8..1110404b0b 100644
--- a/projects/aca-playwright-shared/src/page-objects/components/dataTable/data-table.component.ts
+++ b/projects/aca-playwright-shared/src/page-objects/components/dataTable/data-table.component.ts
@@ -80,7 +80,7 @@ export class DataTableComponent extends BaseComponent {
*
* @returns reference to cell element which contains link.
*/
- getCellLinkByName = (name: string): Locator => this.getChild('.adf-cell-value span', { hasText: name });
+ getCellLinkByName = (name: string): Locator => this.getChild('.adf-cell-value span[role="link"]', { hasText: name });
/**
* Method used in cases where we want to localize the element by [aria-label]