mirror of https://github.com/ghostfolio/ghostfolio
				
				
			
							committed by
							
								
								GitHub
							
						
					
				
				 20 changed files with 541 additions and 3 deletions
			
			
		@ -0,0 +1,52 @@ | 
				
			|||||
 | 
					import { FocusableOption } from '@angular/cdk/a11y'; | 
				
			||||
 | 
					import { | 
				
			||||
 | 
					  ChangeDetectionStrategy, | 
				
			||||
 | 
					  ChangeDetectorRef, | 
				
			||||
 | 
					  Component, | 
				
			||||
 | 
					  ElementRef, | 
				
			||||
 | 
					  EventEmitter, | 
				
			||||
 | 
					  HostBinding, | 
				
			||||
 | 
					  Input, | 
				
			||||
 | 
					  Output, | 
				
			||||
 | 
					  ViewChild | 
				
			||||
 | 
					} from '@angular/core'; | 
				
			||||
 | 
					import { Position } from '@ghostfolio/common/interfaces'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					@Component({ | 
				
			||||
 | 
					  changeDetection: ChangeDetectionStrategy.OnPush, | 
				
			||||
 | 
					  selector: 'gf-assistant-list-item', | 
				
			||||
 | 
					  templateUrl: './assistant-list-item.html', | 
				
			||||
 | 
					  styleUrls: ['./assistant-list-item.scss'] | 
				
			||||
 | 
					}) | 
				
			||||
 | 
					export class AssistantListItemComponent implements FocusableOption { | 
				
			||||
 | 
					  @HostBinding('attr.tabindex') tabindex = -1; | 
				
			||||
 | 
					  @HostBinding('class.has-focus') get getHasFocus() { | 
				
			||||
 | 
					    return this.hasFocus; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @Input() holding: Position; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @Output() clicked = new EventEmitter<void>(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @ViewChild('link') public linkElement: ElementRef; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public hasFocus = false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public constructor(private changeDetectorRef: ChangeDetectorRef) {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public focus() { | 
				
			||||
 | 
					    this.hasFocus = true; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.changeDetectorRef.markForCheck(); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public onClick() { | 
				
			||||
 | 
					    this.clicked.emit(); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public removeFocus() { | 
				
			||||
 | 
					    this.hasFocus = false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.changeDetectorRef.markForCheck(); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,12 @@ | 
				
			|||||
 | 
					<a | 
				
			||||
 | 
					  #link | 
				
			||||
 | 
					  class="d-block px-2 py-1 text-truncate" | 
				
			||||
 | 
					  [queryParams]="{ | 
				
			||||
 | 
					    dataSource: holding?.dataSource, | 
				
			||||
 | 
					    positionDetailDialog: true, | 
				
			||||
 | 
					    symbol: holding?.symbol | 
				
			||||
 | 
					  }" | 
				
			||||
 | 
					  [routerLink]="['/portfolio', 'holdings']" | 
				
			||||
 | 
					  (click)="onClick()" | 
				
			||||
 | 
					  >{{ holding?.name }}</a | 
				
			||||
 | 
					> | 
				
			||||
@ -0,0 +1,12 @@ | 
				
			|||||
 | 
					import { CommonModule } from '@angular/common'; | 
				
			||||
 | 
					import { NgModule } from '@angular/core'; | 
				
			||||
 | 
					import { RouterModule } from '@angular/router'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					import { AssistantListItemComponent } from './assistant-list-item.component'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					@NgModule({ | 
				
			||||
 | 
					  declarations: [AssistantListItemComponent], | 
				
			||||
 | 
					  exports: [AssistantListItemComponent], | 
				
			||||
 | 
					  imports: [CommonModule, RouterModule] | 
				
			||||
 | 
					}) | 
				
			||||
 | 
					export class GfAssistantListItemModule {} | 
				
			||||
@ -0,0 +1,19 @@ | 
				
			|||||
 | 
					:host { | 
				
			||||
 | 
					  display: block; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  &.has-focus { | 
				
			||||
 | 
					    background-color: rgba(var(--palette-primary-500), 1); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    a { | 
				
			||||
 | 
					      color: rgba(var(--light-primary-text)); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					:host-context(.is-dark-theme) { | 
				
			||||
 | 
					  &.has-focus { | 
				
			||||
 | 
					    a { | 
				
			||||
 | 
					      color: rgba(var(--dark-primary-text)); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,219 @@ | 
				
			|||||
 | 
					import { FocusKeyManager } from '@angular/cdk/a11y'; | 
				
			||||
 | 
					import { | 
				
			||||
 | 
					  ChangeDetectionStrategy, | 
				
			||||
 | 
					  ChangeDetectorRef, | 
				
			||||
 | 
					  Component, | 
				
			||||
 | 
					  ElementRef, | 
				
			||||
 | 
					  EventEmitter, | 
				
			||||
 | 
					  HostListener, | 
				
			||||
 | 
					  Input, | 
				
			||||
 | 
					  OnDestroy, | 
				
			||||
 | 
					  OnInit, | 
				
			||||
 | 
					  Output, | 
				
			||||
 | 
					  QueryList, | 
				
			||||
 | 
					  ViewChild, | 
				
			||||
 | 
					  ViewChildren | 
				
			||||
 | 
					} from '@angular/core'; | 
				
			||||
 | 
					import { FormControl } from '@angular/forms'; | 
				
			||||
 | 
					import { MatMenuTrigger } from '@angular/material/menu'; | 
				
			||||
 | 
					import { DataService } from '@ghostfolio/client/services/data.service'; | 
				
			||||
 | 
					import { Position } from '@ghostfolio/common/interfaces'; | 
				
			||||
 | 
					import { EMPTY, Subject, lastValueFrom } from 'rxjs'; | 
				
			||||
 | 
					import { | 
				
			||||
 | 
					  catchError, | 
				
			||||
 | 
					  debounceTime, | 
				
			||||
 | 
					  distinctUntilChanged, | 
				
			||||
 | 
					  map, | 
				
			||||
 | 
					  mergeMap, | 
				
			||||
 | 
					  takeUntil | 
				
			||||
 | 
					} from 'rxjs/operators'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					import { AssistantListItemComponent } from './assistant-list-item/assistant-list-item.component'; | 
				
			||||
 | 
					import { ISearchResults } from './interfaces/interfaces'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					@Component({ | 
				
			||||
 | 
					  changeDetection: ChangeDetectionStrategy.OnPush, | 
				
			||||
 | 
					  selector: 'gf-assistant', | 
				
			||||
 | 
					  templateUrl: './assistant.html', | 
				
			||||
 | 
					  styleUrls: ['./assistant.scss'] | 
				
			||||
 | 
					}) | 
				
			||||
 | 
					export class AssistantComponent implements OnDestroy, OnInit { | 
				
			||||
 | 
					  @HostListener('document:keydown', ['$event']) onKeydown( | 
				
			||||
 | 
					    event: KeyboardEvent | 
				
			||||
 | 
					  ) { | 
				
			||||
 | 
					    if (!this.isOpen) { | 
				
			||||
 | 
					      return; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { | 
				
			||||
 | 
					      for (const item of this.assistantListItems) { | 
				
			||||
 | 
					        item.removeFocus(); | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      this.keyManager.onKeydown(event); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      const currentAssistantListItem = this.getCurrentAssistantListItem(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      if (currentAssistantListItem?.linkElement) { | 
				
			||||
 | 
					        currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ | 
				
			||||
 | 
					          behavior: 'smooth', | 
				
			||||
 | 
					          block: 'center' | 
				
			||||
 | 
					        }); | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    } else if (event.key === 'Enter') { | 
				
			||||
 | 
					      const currentAssistantListItem = this.getCurrentAssistantListItem(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					      if (currentAssistantListItem?.linkElement) { | 
				
			||||
 | 
					        currentAssistantListItem.linkElement.nativeElement?.click(); | 
				
			||||
 | 
					        event.stopPropagation(); | 
				
			||||
 | 
					      } | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @Input() deviceType: string; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @Output() closed = new EventEmitter<void>(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @ViewChild('menuTrigger') menuTriggerElement: MatMenuTrigger; | 
				
			||||
 | 
					  @ViewChild('search', { static: true }) searchElement: ElementRef; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  @ViewChildren(AssistantListItemComponent) | 
				
			||||
 | 
					  assistantListItems: QueryList<AssistantListItemComponent>; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public isLoading = false; | 
				
			||||
 | 
					  public isOpen = false; | 
				
			||||
 | 
					  public placeholder = $localize`Find holding...`; | 
				
			||||
 | 
					  public searchFormControl = new FormControl(''); | 
				
			||||
 | 
					  public searchResults: ISearchResults = { | 
				
			||||
 | 
					    holdings: [] | 
				
			||||
 | 
					  }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  private keyManager: FocusKeyManager<AssistantListItemComponent>; | 
				
			||||
 | 
					  private unsubscribeSubject = new Subject<void>(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public constructor( | 
				
			||||
 | 
					    private changeDetectorRef: ChangeDetectorRef, | 
				
			||||
 | 
					    private dataService: DataService | 
				
			||||
 | 
					  ) {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public ngOnInit() { | 
				
			||||
 | 
					    this.searchFormControl.valueChanges | 
				
			||||
 | 
					      .pipe( | 
				
			||||
 | 
					        map((searchTerm) => { | 
				
			||||
 | 
					          this.isLoading = true; | 
				
			||||
 | 
					          this.searchResults = { | 
				
			||||
 | 
					            holdings: [] | 
				
			||||
 | 
					          }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          this.changeDetectorRef.markForCheck(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          return searchTerm; | 
				
			||||
 | 
					        }), | 
				
			||||
 | 
					        debounceTime(300), | 
				
			||||
 | 
					        distinctUntilChanged(), | 
				
			||||
 | 
					        mergeMap(async (searchTerm) => { | 
				
			||||
 | 
					          const result = <ISearchResults>{ | 
				
			||||
 | 
					            holdings: [] | 
				
			||||
 | 
					          }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          try { | 
				
			||||
 | 
					            if (searchTerm) { | 
				
			||||
 | 
					              return await this.getSearchResults(searchTerm); | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					          } catch {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					          return result; | 
				
			||||
 | 
					        }), | 
				
			||||
 | 
					        takeUntil(this.unsubscribeSubject) | 
				
			||||
 | 
					      ) | 
				
			||||
 | 
					      .subscribe((searchResults) => { | 
				
			||||
 | 
					        this.searchResults = searchResults; | 
				
			||||
 | 
					        this.isLoading = false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        this.changeDetectorRef.markForCheck(); | 
				
			||||
 | 
					      }); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public async initialize() { | 
				
			||||
 | 
					    this.isLoading = true; | 
				
			||||
 | 
					    this.keyManager = new FocusKeyManager(this.assistantListItems).withWrap(); | 
				
			||||
 | 
					    this.searchResults = { | 
				
			||||
 | 
					      holdings: [] | 
				
			||||
 | 
					    }; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    for (const item of this.assistantListItems) { | 
				
			||||
 | 
					      item.removeFocus(); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.searchFormControl.setValue(''); | 
				
			||||
 | 
					    setTimeout(() => { | 
				
			||||
 | 
					      this.searchElement?.nativeElement?.focus(); | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.isLoading = false; | 
				
			||||
 | 
					    this.setIsOpen(true); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.changeDetectorRef.markForCheck(); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public onCloseAssistant() { | 
				
			||||
 | 
					    this.setIsOpen(false); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    this.closed.emit(); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public setIsOpen(aIsOpen: boolean) { | 
				
			||||
 | 
					    this.isOpen = aIsOpen; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  public ngOnDestroy() { | 
				
			||||
 | 
					    this.unsubscribeSubject.next(); | 
				
			||||
 | 
					    this.unsubscribeSubject.complete(); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  private getCurrentAssistantListItem() { | 
				
			||||
 | 
					    return this.assistantListItems.find(({ getHasFocus }) => { | 
				
			||||
 | 
					      return getHasFocus; | 
				
			||||
 | 
					    }); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  private async getSearchResults(aSearchTerm: string) { | 
				
			||||
 | 
					    let holdings: Position[] = []; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    try { | 
				
			||||
 | 
					      holdings = await lastValueFrom(this.searchHolding(aSearchTerm)); | 
				
			||||
 | 
					      holdings = holdings.slice( | 
				
			||||
 | 
					        0, | 
				
			||||
 | 
					        AssistantComponent.SEARCH_RESULTS_DEFAULT_LIMIT | 
				
			||||
 | 
					      ); | 
				
			||||
 | 
					    } catch {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    return { | 
				
			||||
 | 
					      holdings | 
				
			||||
 | 
					    }; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  private searchHolding(aSearchTerm: string) { | 
				
			||||
 | 
					    return this.dataService | 
				
			||||
 | 
					      .fetchPositions({ | 
				
			||||
 | 
					        filters: [ | 
				
			||||
 | 
					          { | 
				
			||||
 | 
					            id: aSearchTerm, | 
				
			||||
 | 
					            type: 'SEARCH_QUERY' | 
				
			||||
 | 
					          } | 
				
			||||
 | 
					        ], | 
				
			||||
 | 
					        range: '1d' | 
				
			||||
 | 
					      }) | 
				
			||||
 | 
					      .pipe( | 
				
			||||
 | 
					        catchError(() => { | 
				
			||||
 | 
					          return EMPTY; | 
				
			||||
 | 
					        }), | 
				
			||||
 | 
					        map(({ positions }) => { | 
				
			||||
 | 
					          return positions; | 
				
			||||
 | 
					        }), | 
				
			||||
 | 
					        takeUntil(this.unsubscribeSubject) | 
				
			||||
 | 
					      ); | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,63 @@ | 
				
			|||||
 | 
					<div | 
				
			||||
 | 
					  [style.width]="deviceType === 'mobile' ? '85vw' : '30rem'" | 
				
			||||
 | 
					  (click)="$event.stopPropagation();" | 
				
			||||
 | 
					  (keydown.tab)="$event.stopPropagation()" | 
				
			||||
 | 
					> | 
				
			||||
 | 
					  <div class="align-items-center d-flex search-container"> | 
				
			||||
 | 
					    <ion-icon class="ml-2 mr-0" name="search-outline"></ion-icon> | 
				
			||||
 | 
					    <input | 
				
			||||
 | 
					      #search | 
				
			||||
 | 
					      autocomplete="off" | 
				
			||||
 | 
					      autocorrect="off" | 
				
			||||
 | 
					      class="border-0 p-2 w-100" | 
				
			||||
 | 
					      name="search" | 
				
			||||
 | 
					      type="text" | 
				
			||||
 | 
					      [formControl]="searchFormControl" | 
				
			||||
 | 
					      [placeholder]="placeholder" | 
				
			||||
 | 
					    /> | 
				
			||||
 | 
					    <div | 
				
			||||
 | 
					      *ngIf="deviceType !== 'mobile' && !searchFormControl.value" | 
				
			||||
 | 
					      class="hot-key-hint mx-1 px-1" | 
				
			||||
 | 
					    > | 
				
			||||
 | 
					      / | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					    <button | 
				
			||||
 | 
					      *ngIf="searchFormControl.value" | 
				
			||||
 | 
					      class="h-100 no-min-width px-3 rounded-0" | 
				
			||||
 | 
					      mat-button | 
				
			||||
 | 
					      (click)="initialize()" | 
				
			||||
 | 
					    > | 
				
			||||
 | 
					      <ion-icon class="m-0" name="close-circle-outline"></ion-icon> | 
				
			||||
 | 
					    </button> | 
				
			||||
 | 
					    <button | 
				
			||||
 | 
					      *ngIf="!searchFormControl.value" | 
				
			||||
 | 
					      class="h-100 no-min-width px-3 rounded-0" | 
				
			||||
 | 
					      mat-button | 
				
			||||
 | 
					      (click)="onCloseAssistant()" | 
				
			||||
 | 
					    > | 
				
			||||
 | 
					      <ion-icon class="m-0" name="close-outline"></ion-icon> | 
				
			||||
 | 
					    </button> | 
				
			||||
 | 
					  </div> | 
				
			||||
 | 
					  <div class="overflow-auto py-3 result-container"> | 
				
			||||
 | 
					    <div> | 
				
			||||
 | 
					      <div class="h6 mb-1 px-2" i18n>Holdings</div> | 
				
			||||
 | 
					      <gf-assistant-list-item | 
				
			||||
 | 
					        *ngFor="let holding of searchResults?.holdings" | 
				
			||||
 | 
					        [holding]="holding" | 
				
			||||
 | 
					        (clicked)="onCloseAssistant()" | 
				
			||||
 | 
					      /> | 
				
			||||
 | 
					      <ng-container *ngIf="searchResults?.holdings?.length === 0"> | 
				
			||||
 | 
					        <ngx-skeleton-loader | 
				
			||||
 | 
					          *ngIf="isLoading" | 
				
			||||
 | 
					          animation="pulse" | 
				
			||||
 | 
					          class="mx-2" | 
				
			||||
 | 
					          [theme]="{ | 
				
			||||
 | 
					            height: '1.5rem', | 
				
			||||
 | 
					            width: '100%' | 
				
			||||
 | 
					          }" | 
				
			||||
 | 
					        ></ngx-skeleton-loader> | 
				
			||||
 | 
					        <div *ngIf="!isLoading" class="px-2 py-1" i18n>No entries...</div> | 
				
			||||
 | 
					      </ng-container> | 
				
			||||
 | 
					    </div> | 
				
			||||
 | 
					  </div> | 
				
			||||
 | 
					</div> | 
				
			||||
@ -0,0 +1,25 @@ | 
				
			|||||
 | 
					import { CommonModule } from '@angular/common'; | 
				
			||||
 | 
					import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; | 
				
			||||
 | 
					import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | 
				
			||||
 | 
					import { MatButtonModule } from '@angular/material/button'; | 
				
			||||
 | 
					import { RouterModule } from '@angular/router'; | 
				
			||||
 | 
					import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					import { GfAssistantListItemModule } from './assistant-list-item/assistant-list-item.module'; | 
				
			||||
 | 
					import { AssistantComponent } from './assistant.component'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					@NgModule({ | 
				
			||||
 | 
					  declarations: [AssistantComponent], | 
				
			||||
 | 
					  exports: [AssistantComponent], | 
				
			||||
 | 
					  imports: [ | 
				
			||||
 | 
					    CommonModule, | 
				
			||||
 | 
					    FormsModule, | 
				
			||||
 | 
					    GfAssistantListItemModule, | 
				
			||||
 | 
					    MatButtonModule, | 
				
			||||
 | 
					    NgxSkeletonLoaderModule, | 
				
			||||
 | 
					    ReactiveFormsModule, | 
				
			||||
 | 
					    RouterModule | 
				
			||||
 | 
					  ], | 
				
			||||
 | 
					  schemas: [CUSTOM_ELEMENTS_SCHEMA] | 
				
			||||
 | 
					}) | 
				
			||||
 | 
					export class GfAssistantModule {} | 
				
			||||
@ -0,0 +1,37 @@ | 
				
			|||||
 | 
					:host { | 
				
			||||
 | 
					  display: block; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .result-container { | 
				
			||||
 | 
					    max-height: 15rem; | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					  .search-container { | 
				
			||||
 | 
					    border-bottom: 1px solid rgba(var(--dark-dividers)); | 
				
			||||
 | 
					    height: 2.5rem; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    input { | 
				
			||||
 | 
					      background: transparent; | 
				
			||||
 | 
					      outline: 0; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    .hot-key-hint { | 
				
			||||
 | 
					      border: 1px solid rgba(var(--dark-dividers)); | 
				
			||||
 | 
					      border-radius: 0.25rem; | 
				
			||||
 | 
					      cursor: default; | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					:host-context(.is-dark-theme) { | 
				
			||||
 | 
					  .search-container { | 
				
			||||
 | 
					    border-color: rgba(var(--light-dividers)); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    input { | 
				
			||||
 | 
					      color: rgba(var(--light-primary-text)); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    .hot-key-hint { | 
				
			||||
 | 
					      border-color: rgba(var(--light-dividers)); | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					  } | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1 @@ | 
				
			|||||
 | 
					export * from './assistant.module'; | 
				
			||||
@ -0,0 +1,5 @@ | 
				
			|||||
 | 
					import { Position } from '@ghostfolio/common/interfaces'; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					export interface ISearchResults { | 
				
			||||
 | 
					  holdings: Position[]; | 
				
			||||
 | 
					} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue