Merge pull request #517 from mempool/simon/bisq-address-prefix-search

Handle the 'B' prefix in the search bar autocomplete on /bisq
This commit is contained in:
wiz 2021-05-14 03:42:15 +09:00 committed by GitHub
commit 3ffa60db1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 18 deletions

View File

@ -1,7 +1,7 @@
<form [formGroup]="searchForm" (submit)="searchForm.valid && search()" novalidate> <form [formGroup]="searchForm" (submit)="searchForm.valid && search()" novalidate>
<div class="d-flex"> <div class="d-flex">
<div class="search-box-container mr-2"> <div class="search-box-container mr-2">
<input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearch" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="TXID, block height, hash or address"> <input #instance="ngbTypeahead" [ngbTypeahead]="typeaheadSearchFn" (selectItem)="itemSelected()" (focus)="focus$.next($any($event).target.value)" (click)="click$.next($any($event).target.value)" formControlName="searchText" type="text" class="form-control" i18n-placeholder="search-form.searchbar-placeholder" placeholder="TXID, block height, hash or address">
</div> </div>
<div> <div>
<button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary"><fa-icon [icon]="['fas', 'search']" [fixedWidth]="true" i18n-title="search-form.search-title" title="Search"></fa-icon></button> <button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary"><fa-icon [icon]="['fas', 'search']" [fixedWidth]="true" i18n-title="search-form.search-title" title="Search"></fa-icon></button>

View File

@ -4,7 +4,7 @@ import { Router } from '@angular/router';
import { AssetsService } from 'src/app/services/assets.service'; import { AssetsService } from 'src/app/services/assets.service';
import { StateService } from 'src/app/services/state.service'; import { StateService } from 'src/app/services/state.service';
import { Observable, of, Subject, merge } from 'rxjs'; import { Observable, of, Subject, merge } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, filter, catchError } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, switchMap, filter, catchError, map } from 'rxjs/operators';
import { ElectrsApiService } from 'src/app/services/electrs-api.service'; import { ElectrsApiService } from 'src/app/services/electrs-api.service';
import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
@ -18,11 +18,12 @@ export class SearchFormComponent implements OnInit {
network = ''; network = '';
assets: object = {}; assets: object = {};
isSearching = false; isSearching = false;
typeaheadSearchFn: ((text: Observable<string>) => Observable<readonly any[]>);
searchForm: FormGroup; searchForm: FormGroup;
@Output() searchTriggered = new EventEmitter(); @Output() searchTriggered = new EventEmitter();
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,87})$/; regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[bB]?[a-z]{2,5}1[ac-hj-np-z02-9]{8,87})$/;
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/; regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
regexTransaction = /^[a-fA-F0-9]{64}$/; regexTransaction = /^[a-fA-F0-9]{64}$/;
regexBlockheight = /^[0-9]+$/; regexBlockheight = /^[0-9]+$/;
@ -31,21 +32,6 @@ export class SearchFormComponent implements OnInit {
focus$ = new Subject<string>(); focus$ = new Subject<string>();
click$ = new Subject<string>(); click$ = new Subject<string>();
typeaheadSearch = (text$: Observable<string>) => {
const debouncedText$ = text$.pipe(debounceTime(200), distinctUntilChanged());
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
const inputFocus$ = this.focus$;
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
.pipe(
switchMap((text) => {
if (!text.length) { return of([]); }
return this.electrsApiService.getAddressesByPrefix$(text)
.pipe(catchError(() => of([])));
})
);
}
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router, private router: Router,
@ -55,11 +41,13 @@ export class SearchFormComponent implements OnInit {
) { } ) { }
ngOnInit() { ngOnInit() {
this.typeaheadSearchFn = this.typeaheadSearch;
this.stateService.networkChanged$.subscribe((network) => this.network = network); this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.searchForm = this.formBuilder.group({ this.searchForm = this.formBuilder.group({
searchText: ['', Validators.required], searchText: ['', Validators.required],
}); });
if (this.network === 'liquid') { if (this.network === 'liquid') {
this.assetsService.getAssetsMinimalJson$ this.assetsService.getAssetsMinimalJson$
.subscribe((assets) => { .subscribe((assets) => {
@ -68,6 +56,37 @@ export class SearchFormComponent implements OnInit {
} }
} }
typeaheadSearch = (text$: Observable<string>) => {
const debouncedText$ = text$.pipe(
map((text) => {
if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) {
return text.substr(1);
}
return text;
}),
debounceTime(200),
distinctUntilChanged()
);
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
const inputFocus$ = this.focus$;
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
.pipe(
switchMap((text) => {
if (!text.length) {
return of([]);
}
return this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([])));
}),
map((result: string[]) => {
if (this.network === 'bisq') {
return result.map((address: string) => 'B' + address);
}
return result;
})
);
}
itemSelected() { itemSelected() {
setTimeout(() => this.search()); setTimeout(() => this.search());
} }