<p *ngFor="let line of clog">
<span class="timestamp">{{ line.stamp | cdDate }}</span>
<span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
- <span class="message">{{ line.message }}</span>
+ <span class="message"
+ [innerHTML]="line.message | searchHighlight: search"></span>
</p>
<ng-container *ngIf="clog.length != 0 else noEntriesTpl"></ng-container>
<p *ngFor="let line of audit_log">
<span class="timestamp">{{ line.stamp | cdDate }}</span>
<span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
- <span class="message">{{ line.message }}</span>
+ <span class="message"
+ [innerHTML]="line.message | searchHighlight: search"></span>
</p>
<ng-container *ngIf="audit_log.length != 0 else noEntriesTpl"></ng-container>
abstractFilters(): any {
const priority = this.priority;
- const key = this.search.toLowerCase().replace(/,/g, '');
-
+ const key = this.search.toLowerCase();
let yearMonthDay: string;
if (this.selectedDate) {
const m = this.selectedDate.month;
import { RelativeDatePipe } from './relative-date.pipe';
import { RoundPipe } from './round.pipe';
import { SanitizeHtmlPipe } from './sanitize-html.pipe';
+import { SearchHighlightPipe } from './search-highlight.pipe';
import { TruncatePipe } from './truncate.pipe';
import { UpperFirstPipe } from './upper-first.pipe';
DurationPipe,
MapPipe,
TruncatePipe,
- SanitizeHtmlPipe
+ SanitizeHtmlPipe,
+ SearchHighlightPipe
],
exports: [
ArrayPipe,
DurationPipe,
MapPipe,
TruncatePipe,
- SanitizeHtmlPipe
+ SanitizeHtmlPipe,
+ SearchHighlightPipe
],
providers: [
ArrayPipe,
--- /dev/null
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { SearchHighlightPipe } from './search-highlight.pipe';
+
+describe('SearchHighlightPipe', () => {
+ let pipe: SearchHighlightPipe;
+
+ configureTestBed({
+ providers: [SearchHighlightPipe]
+ });
+
+ beforeEach(() => {
+ pipe = TestBed.inject(SearchHighlightPipe);
+ });
+
+ it('create an instance', () => {
+ expect(pipe).toBeTruthy();
+ });
+
+ it('transforms with a matching keyword ', () => {
+ const value = 'overall HEALTH_WARN Dashboard debug mode is enabled';
+ const args = 'Dashboard';
+ const expected = 'overall HEALTH_WARN <mark>Dashboard</mark> debug mode is enabled';
+
+ expect(pipe.transform(value, args)).toEqual(expected);
+ });
+
+ it('transforms with a matching keyword having regex character', () => {
+ const value = 'loreum ipsum .? dolor sit amet';
+ const args = '.?';
+ const expected = 'loreum ipsum <mark>.?</mark> dolor sit amet';
+
+ expect(pipe.transform(value, args)).toEqual(expected);
+ });
+
+ it('transforms with empty search keyword', () => {
+ const value = 'overall HEALTH_WARN Dashboard debug mode is enabled';
+ expect(pipe.transform(value, '')).toBe(value);
+ });
+});
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'searchHighlight'
+})
+export class SearchHighlightPipe implements PipeTransform {
+ transform(value: string, args: string): string {
+ if (!args) {
+ return value;
+ }
+ args = this.escapeRegExp(args);
+ const regex = new RegExp(args, 'gi');
+ const match = value.match(regex);
+
+ if (!match) {
+ return value;
+ }
+
+ return value.replace(regex, '<mark>$&</mark>');
+ }
+
+ private escapeRegExp(str: string) {
+ // $& means the whole matched string
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+}