Move SearchHighlightDirective to AutocompleterModule in order to use it in the project autocompleter. The highlightDirective needed to be extended so that it can highlight dynamically and not only once at the first trigger.

pull/10674/head
Henriette Darge 2 years ago
parent 6d3df992f3
commit 4f864d59a6
  1. 4
      frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.component.html
  2. 7
      frontend/src/app/shared/components/autocompleter/openproject-autocompleter.module.ts
  3. 10
      frontend/src/app/shared/components/autocompleter/project-autocompleter/project-autocompleter.component.html
  4. 8
      frontend/src/app/shared/components/autocompleter/project-autocompleter/project-autocompleter.component.ts
  5. 17
      frontend/src/app/shared/directives/search-highlight.directive.ts
  6. 5
      frontend/src/app/shared/shared.module.ts

@ -98,7 +98,7 @@
let-search="searchTerm" let-search="searchTerm"
> >
<ng-container <ng-container
[ngTemplateOutlet]="optionTemplate ? optionTemplate : defaultOption" [ngTemplateOutlet]="optionTemplate || defaultOption"
[ngTemplateOutletContext]="{$implicit:item, search:search, index:index }" [ngTemplateOutletContext]="{$implicit:item, search:search, index:index }"
></ng-container> ></ng-container>
</ng-template> </ng-template>
@ -145,7 +145,7 @@
<div class="op-autocompleter--wp-content"> <div class="op-autocompleter--wp-content">
<span <span
[ngOptionHighlight]="search" [ngOptionHighlight]="search"
[textContent]="item.project?.name" [textContent]="item.project?.name"
class="op-autocompleter--wp-project" class="op-autocompleter--wp-project"
></span> ></span>

@ -1,6 +1,9 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import {
FormsModule,
ReactiveFormsModule,
} from '@angular/forms';
import { DynamicModule } from 'ng-dynamic-component'; import { DynamicModule } from 'ng-dynamic-component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { DragulaModule } from 'ng2-dragula'; import { DragulaModule } from 'ng2-dragula';
@ -21,6 +24,7 @@ import { OpAutocompleterLabelTemplateDirective } from 'core-app/shared/component
import { OpAutocompleterHeaderTemplateDirective } from 'core-app/shared/components/autocompleter/op-autocompleter/directives/op-autocompleter-header-template.directive'; import { OpAutocompleterHeaderTemplateDirective } from 'core-app/shared/components/autocompleter/op-autocompleter/directives/op-autocompleter-header-template.directive';
import { CreateAutocompleterComponent } from 'core-app/shared/components/autocompleter/create-autocompleter/create-autocompleter.component'; import { CreateAutocompleterComponent } from 'core-app/shared/components/autocompleter/create-autocompleter/create-autocompleter.component';
import { OpAutocompleterFooterTemplateDirective } from 'core-app/shared/components/autocompleter/autocompleter-footer-template/op-autocompleter-footer-template.directive'; import { OpAutocompleterFooterTemplateDirective } from 'core-app/shared/components/autocompleter/autocompleter-footer-template/op-autocompleter-footer-template.directive';
import { OpSearchHighlightDirective } from 'core-app/shared/directives/search-highlight.directive';
export const OPENPROJECT_AUTOCOMPLETE_COMPONENTS = [ export const OPENPROJECT_AUTOCOMPLETE_COMPONENTS = [
CreateAutocompleterComponent, CreateAutocompleterComponent,
@ -37,6 +41,7 @@ export const OPENPROJECT_AUTOCOMPLETE_COMPONENTS = [
OpAutocompleterLabelTemplateDirective, OpAutocompleterLabelTemplateDirective,
OpAutocompleterHeaderTemplateDirective, OpAutocompleterHeaderTemplateDirective,
OpAutocompleterFooterTemplateDirective, OpAutocompleterFooterTemplateDirective,
OpSearchHighlightDirective,
]; ];
@NgModule({ @NgModule({

@ -10,10 +10,10 @@
appendTo="body" appendTo="body"
(change)="writeValue($event)" (change)="writeValue($event)"
> >
<ng-template <ng-template
op-autocompleter-label-tmp ng-label-tmp
let-item let-item
> >
<span class="ng-value-label">{{ item.name }}</span> <span class="ng-value-label">{{ item.name }}</span>
@ -22,12 +22,12 @@
<ng-template <ng-template
op-autocompleter-option-tmp op-autocompleter-option-tmp
let-item let-item
let-search="searchTerm" let-search="search"
> >
<div <div
class="ng-option-label ellipsis" class="ng-option-label ellipsis"
[ngStyle]="{ 'padding-left.px': item.numberOfAncestors * 20 }" [ngStyle]="{ 'padding-left.px': item.numberOfAncestors * 20 }"
[ngOptionHighlight]="search" [opSearchHighlight]="search"
>{{ item.name }}</div> >{{ item.name }}</div>
<div <div
*ngIf="item.disabledReason" *ngIf="item.disabledReason"
@ -39,7 +39,7 @@
<input <input
#hiddenInput #hiddenInput
[name]="name" [name]="name"
type="hidden" type="hidden"
[value]="plainValue" [value]="plainValue"
/> />

@ -94,7 +94,7 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
@Input() public multiple = false; @Input() public multiple = false;
@Input() public labelForId:string = ''; @Input() public labelForId = '';
@Input() public apiFilters:ApiV3ListFilter[] = []; @Input() public apiFilters:ApiV3ListFilter[] = [];
@ -128,7 +128,7 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
@Output('valueChange') valueChange = new EventEmitter<IProjectAutocompleterData|IProjectAutocompleterData[]|null>(); @Output('valueChange') valueChange = new EventEmitter<IProjectAutocompleterData|IProjectAutocompleterData[]|null>();
@ViewChild('hiddenInput') hiddenInput: ElementRef; @ViewChild('hiddenInput') hiddenInput:ElementRef;
constructor( constructor(
public elementRef:ElementRef, public elementRef:ElementRef,
@ -147,7 +147,7 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
return getPaginatedResults<IProject>( return getPaginatedResults<IProject>(
(params) => { (params) => {
const filters:ApiV3ListFilter[] = [...this.apiFilters]; const filters:ApiV3ListFilter[] = [...this.apiFilters];
if (searchTerm.length) { if (searchTerm.length) {
filters.push(['name_and_identifier', '~', [searchTerm]]); filters.push(['name_and_identifier', '~', [searchTerm]]);
} }
@ -168,7 +168,7 @@ export class ProjectAutocompleterComponent implements ControlValueAccessor {
], ],
...params, ...params,
}; };
const collectionURL = listParamsString(fullParams) + '&' + url.searchParams.toString(); const collectionURL = `${listParamsString(fullParams)}&${url.searchParams.toString()}`;
url.searchParams.forEach((key) => url.searchParams.delete(key)); url.searchParams.forEach((key) => url.searchParams.delete(key));
return this.http.get<IHALCollection<IProject>>(url.toString() + collectionURL); return this.http.get<IHALCollection<IProject>>(url.toString() + collectionURL);
}, },

@ -14,12 +14,13 @@ export class OpSearchHighlightDirective implements AfterViewChecked {
constructor(readonly elementRef:ElementRef) { } constructor(readonly elementRef:ElementRef) { }
ngAfterViewChecked():void { ngAfterViewChecked():void {
let el = this.elementRef.nativeElement as HTMLElement;
el = this.cleanUpOldHighlighting(el);
if (!this.query) { if (!this.query) {
return; return;
} }
const el = this.elementRef.nativeElement as HTMLElement;
const textNode = Array.from(el.childNodes).find((n:Node) => n.nodeType === n.TEXT_NODE) as Node; const textNode = Array.from(el.childNodes).find((n:Node) => n.nodeType === n.TEXT_NODE) as Node;
const content = textNode?.textContent || ''; const content = textNode?.textContent || '';
if (!content) { if (!content) {
@ -40,4 +41,16 @@ export class OpSearchHighlightDirective implements AfterViewChecked {
newNode.innerHTML = `${start}<span class="op-search-highlight">${result}</span>${end}`; newNode.innerHTML = `${start}<span class="op-search-highlight">${result}</span>${end}`;
el.replaceChild(newNode, textNode); el.replaceChild(newNode, textNode);
} }
private cleanUpOldHighlighting(el:HTMLElement):HTMLElement {
if (el.children.length > 0) {
const unifiedLabelText = Array.from(el.children, ({ textContent }) => textContent?.trim()).join('');
// eslint-disable-next-line no-param-reassign
el.innerHTML = '';
// eslint-disable-next-line no-param-reassign
el.innerText = unifiedLabelText;
}
return el;
}
} }

@ -65,7 +65,6 @@ import {
highlightColSelector, highlightColSelector,
OpHighlightColDirective, OpHighlightColDirective,
} from './directives/highlight-col/highlight-col.directive'; } from './directives/highlight-col/highlight-col.directive';
import { OpSearchHighlightDirective } from './directives/search-highlight.directive';
import { CopyToClipboardDirective } from './components/copy-to-clipboard/copy-to-clipboard.directive'; import { CopyToClipboardDirective } from './components/copy-to-clipboard/copy-to-clipboard.directive';
import { OpDateTimeComponent } from './components/date/op-date-time.component'; import { OpDateTimeComponent } from './components/date/op-date-time.component';
@ -172,8 +171,6 @@ export function bootstrapModule(injector:Injector) {
// Table highlight // Table highlight
OpHighlightColDirective, OpHighlightColDirective,
OpSearchHighlightDirective,
ResizerComponent, ResizerComponent,
TablePaginationComponent, TablePaginationComponent,
@ -236,8 +233,6 @@ export function bootstrapModule(injector:Injector) {
TablePaginationComponent, TablePaginationComponent,
SortHeaderDirective, SortHeaderDirective,
OpSearchHighlightDirective,
// Zen mode button // Zen mode button
ZenModeButtonComponent, ZenModeButtonComponent,

Loading…
Cancel
Save