mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-24 17:31:52 +00:00
[ACS-6427] Add search highlighting (#3637)
* [ACS-6427] Initial commit for search highlights * [ACS-6427] Add correct highlighting config, handle highlights in search results * [ACS-6427] CR fixes * [ACS-6427] CR fix * [ACS-6427] Locator fix * [ACS-6427] E2E fix --------- Co-authored-by: swapnil.verma <swapnil.verma@globallogic.com>
This commit is contained in:
@@ -513,7 +513,7 @@ describe('Search filters', () => {
|
|||||||
await peopleFilter.closeDialog();
|
await peopleFilter.closeDialog();
|
||||||
|
|
||||||
await searchInput.clickSearchButton();
|
await searchInput.clickSearchButton();
|
||||||
await searchInput.searchFor(fileJpgUser1.name);
|
await searchInput.searchFor(`${fileJpgUser1.name}*`);
|
||||||
await dataTable.waitForBody();
|
await dataTable.waitForBody();
|
||||||
|
|
||||||
const expectedUsers2 = [`${user1} ${user1}`];
|
const expectedUsers2 = [`${user1} ${user1}`];
|
||||||
|
@@ -1532,7 +1532,27 @@
|
|||||||
"visible": "app.areCategoriesEnabled"
|
"visible": "app.areCategoriesEnabled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"highlight": {
|
||||||
|
"prefix": "<span class='aca-highlight'>",
|
||||||
|
"postfix": "</span>",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"field": "cm:title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:description",
|
||||||
|
"snippetCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:content",
|
||||||
|
"snippetCount": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "app.search.dublin-core",
|
"id": "app.search.dublin-core",
|
||||||
@@ -1688,7 +1708,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"highlight": {
|
||||||
|
"prefix": "<span class='aca-highlight'>",
|
||||||
|
"postfix": "</span>",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"field": "cm:title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:description",
|
||||||
|
"snippetCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:content",
|
||||||
|
"snippetCount": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "app.search.effectivity",
|
"id": "app.search.effectivity",
|
||||||
@@ -1846,8 +1886,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"highlight": {
|
||||||
|
"prefix": "<span class='aca-highlight'>",
|
||||||
|
"postfix": "</span>",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"field": "cm:title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:description",
|
||||||
|
"snippetCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "cm:content",
|
||||||
|
"snippetCount": 1
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
"content-metadata-presets": [
|
"content-metadata-presets": [
|
||||||
|
@@ -1,12 +1,38 @@
|
|||||||
<div class="search-file-name">
|
<div class="search-file-name">
|
||||||
<span tabindex="0" role="link" *ngIf="isFile" (click)="showPreview($event)" (keyup.enter)="showPreview($event)" class="aca-link">
|
<span
|
||||||
{{ name$ | async }}
|
tabindex="0"
|
||||||
</span>
|
role="link"
|
||||||
<span tabindex="0" role="link" *ngIf="!isFile" (click)="navigate($event)" (keyup.enter)="navigate($event)" class="bold aca-link">
|
*ngIf="isFile"
|
||||||
{{ name$ | async }}
|
(click)="showPreview($event)"
|
||||||
</span>
|
(keyup.enter)="showPreview($event)"
|
||||||
<span>{{ title$ | async }}</span>
|
class="aca-link aca-crop-text"
|
||||||
|
[title]="nameStripped"
|
||||||
|
[innerHTML]="name$ | async"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
tabindex="0"
|
||||||
|
role="link"
|
||||||
|
*ngIf="!isFile"
|
||||||
|
(click)="navigate($event)"
|
||||||
|
(keyup.enter)="navigate($event)"
|
||||||
|
class="bold aca-link aca-crop-text"
|
||||||
|
[title]="nameStripped"
|
||||||
|
[innerHTML]="name$ | async"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
data-automation-id="search-results-entry-title"
|
||||||
|
class="aca-crop-text"
|
||||||
|
[title]="titleStripped"
|
||||||
|
[innerHTML]="title$ | async"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
data-automation-id="search-results-entry-description"
|
||||||
|
class="aca-crop-text"
|
||||||
|
[title]="descriptionStripped"
|
||||||
|
[innerHTML]="description$ | async"
|
||||||
|
></div>
|
||||||
<div class="aca-result-location">
|
<div class="aca-result-location">
|
||||||
<aca-location-link [context]="context" [showLocation]="true"></aca-location-link>
|
<aca-location-link [context]="context" [showLocation]="true"></aca-location-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="aca-result-content aca-crop-text" [title]="contentStripped" [innerHTML]="content$ | async"></div>
|
||||||
|
@@ -1,9 +1,26 @@
|
|||||||
.aca-search-results-row {
|
.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 {
|
.aca-result-location {
|
||||||
height: 15px;
|
height: 15px;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.aca-result-content {
|
||||||
|
padding: 0 5px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.aca-link {
|
.aca-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--theme-text-bold-color);
|
color: var(--theme-text-bold-color);
|
||||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<SearchResultsRowComponent>;
|
||||||
|
|
||||||
|
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 <span class='aca-highlight'>random</span> content`]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'cm:name',
|
||||||
|
snippets: [`<span class='aca-highlight'>Random</span>`]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'cm:title',
|
||||||
|
snippets: [`<span class='aca-highlight'>Random</span> title`]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'cm:description',
|
||||||
|
snippets: [`some <span class='aca-highlight'>random</span> 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('<span class="aca-highlight">Random</span>');
|
||||||
|
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(' ( <span class="aca-highlight">Random</span> 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 <span class="aca-highlight">random</span> 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 <span class="aca-highlight">random</span> content...');
|
||||||
|
expect(contentElement.title).toBe('...Interesting random content...');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
});
|
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, OnInit, ViewEncapsulation, ChangeDetectionStrategy, OnDestroy } from '@angular/core';
|
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 { ViewNodeAction, NavigateToFolder } from '@alfresco/aca-shared/store';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { BehaviorSubject, Subject } from 'rxjs';
|
import { BehaviorSubject, Subject } from 'rxjs';
|
||||||
@@ -46,6 +46,9 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
host: { class: 'aca-search-results-row' }
|
host: { class: 'aca-search-results-row' }
|
||||||
})
|
})
|
||||||
export class SearchResultsRowComponent implements OnInit, OnDestroy {
|
export class SearchResultsRowComponent implements OnInit, OnDestroy {
|
||||||
|
private readonly highlightPrefix = "<span class='aca-highlight'>";
|
||||||
|
private readonly highlightPostfix = '</span>';
|
||||||
|
|
||||||
private node: NodeEntry;
|
private node: NodeEntry;
|
||||||
private onDestroy$ = new Subject<boolean>();
|
private onDestroy$ = new Subject<boolean>();
|
||||||
|
|
||||||
@@ -54,6 +57,12 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
name$ = new BehaviorSubject<string>('');
|
name$ = new BehaviorSubject<string>('');
|
||||||
title$ = new BehaviorSubject<string>('');
|
title$ = new BehaviorSubject<string>('');
|
||||||
|
description$ = new BehaviorSubject<string>('');
|
||||||
|
content$ = new BehaviorSubject<string>('');
|
||||||
|
nameStripped = '';
|
||||||
|
titleStripped = '';
|
||||||
|
descriptionStripped = '';
|
||||||
|
contentStripped = '';
|
||||||
|
|
||||||
isFile = false;
|
isFile = false;
|
||||||
|
|
||||||
@@ -86,13 +95,42 @@ export class SearchResultsRowComponent implements OnInit, OnDestroy {
|
|||||||
this.node = this.context.row.node;
|
this.node = this.context.row.node;
|
||||||
this.isFile = this.node.entry.isFile;
|
this.isFile = this.node.entry.isFile;
|
||||||
|
|
||||||
const { name, properties } = this.node.entry;
|
const highlights: SearchEntryHighlight[] = this.node.entry['search']?.['highlight'];
|
||||||
const title = properties ? properties['cm:title'] : '';
|
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.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) {
|
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();
|
event.stopPropagation();
|
||||||
this.store.dispatch(new NavigateToFolder(this.node));
|
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'), '')
|
||||||
|
: '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<SearchResultsRowComponent>;
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -64,14 +64,6 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</data-column>
|
</data-column>
|
||||||
|
|
||||||
<data-column id="app.search.description" key="properties" title="Description" class="adf-expand-cell-3" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true">
|
|
||||||
<ng-template let-context>
|
|
||||||
<span class="adf-datatable-cell-value adf-ellipsis-cell">
|
|
||||||
{{context.row?.node?.entry?.properties && context.row?.node?.entry?.properties['cm:description']}}
|
|
||||||
</span>
|
|
||||||
</ng-template>
|
|
||||||
</data-column>
|
|
||||||
|
|
||||||
<data-column id="app.search.size" key="content.sizeInBytes" type="fileSize" title="APP.DOCUMENT_LIST.COLUMNS.SIZE" class="adf-no-grow-cell adf-ellipsis-cell" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true"></data-column>
|
<data-column id="app.search.size" key="content.sizeInBytes" type="fileSize" title="APP.DOCUMENT_LIST.COLUMNS.SIZE" class="adf-no-grow-cell adf-ellipsis-cell" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true"></data-column>
|
||||||
<data-column id="app.search.modifiedOn" key="modifiedAt" type="date" title="APP.DOCUMENT_LIST.COLUMNS.MODIFIED_ON" class="adf-no-grow-cell adf-ellipsis-cell" format="timeAgo" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true"></data-column>
|
<data-column id="app.search.modifiedOn" key="modifiedAt" type="date" title="APP.DOCUMENT_LIST.COLUMNS.MODIFIED_ON" class="adf-no-grow-cell adf-ellipsis-cell" format="timeAgo" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true"></data-column>
|
||||||
<data-column id="app.search.modifiedBy" key="modifiedByUser.displayName" title="APP.DOCUMENT_LIST.COLUMNS.MODIFIED_BY" class="adf-no-grow-cell adf-ellipsis-cell" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true"></data-column>
|
<data-column id="app.search.modifiedBy" key="modifiedByUser.displayName" title="APP.DOCUMENT_LIST.COLUMNS.MODIFIED_BY" class="adf-no-grow-cell adf-ellipsis-cell" [sortable]="false" *ngIf="!isSmallScreen" [draggable]="true"></data-column>
|
||||||
|
@@ -178,6 +178,11 @@ describe('SearchComponent', () => {
|
|||||||
expect(query).toBe(`(cm:name:"hello*" OR cm:title:"hello*")`);
|
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', () => {
|
it('should format user input as cm:name if configuration not provided', () => {
|
||||||
const query = component.formatSearchQuery('hello');
|
const query = component.formatSearchQuery('hello');
|
||||||
expect(query).toBe(`(cm:name:"hello*")`);
|
expect(query).toBe(`(cm:name:"hello*")`);
|
||||||
|
@@ -206,7 +206,15 @@ export class SearchResultsComponent extends PageComponent implements OnInit {
|
|||||||
term = term.substring(1);
|
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']) {
|
formatSearchQuery(userInput: string, fields = ['cm:name']) {
|
||||||
|
@@ -41,6 +41,7 @@ $page-layout-header-background-color: $background-card-color;
|
|||||||
$search-chip-icon-color: #757575;
|
$search-chip-icon-color: #757575;
|
||||||
$disabled-chip-background-color: #f5f5f5;
|
$disabled-chip-background-color: #f5f5f5;
|
||||||
$contrast-gray: mat.get-color-from-palette($foreground, 'secondary-tex');
|
$contrast-gray: mat.get-color-from-palette($foreground, 'secondary-tex');
|
||||||
|
$search-highlight-background-color: #ffd180;
|
||||||
|
|
||||||
// CSS Variables
|
// CSS Variables
|
||||||
$defaults: (
|
$defaults: (
|
||||||
@@ -86,7 +87,8 @@ $defaults: (
|
|||||||
--theme-page-layout-header-background-color: $page-layout-header-background-color,
|
--theme-page-layout-header-background-color: $page-layout-header-background-color,
|
||||||
--theme-search-chip-icon-color: $search-chip-icon-color,
|
--theme-search-chip-icon-color: $search-chip-icon-color,
|
||||||
--theme-disabled-chip-background-color: $disabled-chip-background-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
|
// propagates SCSS variables into the CSS variables scope
|
||||||
|
@@ -80,7 +80,7 @@ export class DataTableComponent extends BaseComponent {
|
|||||||
*
|
*
|
||||||
* @returns reference to cell element which contains link.
|
* @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]
|
* Method used in cases where we want to localize the element by [aria-label]
|
||||||
|
Reference in New Issue
Block a user