mirror of
https://gitlab.com/walljm/dynamicbible.git
synced 2025-07-25 08:19:50 -04:00
fix autocomplete drop down after search
add word search add autocomplete search to state
This commit is contained in:
parent
078aa1eddd
commit
2748abeffa
@ -10,6 +10,8 @@ import { HttpClientModule } from '@angular/common/http';
|
|||||||
import { SearchPage } from './search/components/search-page/search.page';
|
import { SearchPage } from './search/components/search-page/search.page';
|
||||||
import { PassageComponent } from './search/components/passage/passage.component';
|
import { PassageComponent } from './search/components/passage/passage.component';
|
||||||
import { StrongsComponent } from './search/components/strongs/strongs.component';
|
import { StrongsComponent } from './search/components/strongs/strongs.component';
|
||||||
|
import { WordsComponent } from './search/components/words/words.component';
|
||||||
|
|
||||||
import { VersePickerModalComponent } from './search/components/verse-picker/verse-picker-modal.component';
|
import { VersePickerModalComponent } from './search/components/verse-picker/verse-picker-modal.component';
|
||||||
|
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
@ -55,6 +57,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
|
|||||||
SearchPage,
|
SearchPage,
|
||||||
PassageComponent,
|
PassageComponent,
|
||||||
StrongsComponent,
|
StrongsComponent,
|
||||||
|
WordsComponent,
|
||||||
VersePickerModalComponent,
|
VersePickerModalComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CardItem, OpenData } from '../models/app-state';
|
import { CardItem, OpenData } from '../models/app-state';
|
||||||
|
import { BibleReference } from './bible-reference';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: '',
|
template: '',
|
||||||
@ -55,4 +56,14 @@ export class CardComponent {
|
|||||||
this.onClose.emit(this.cardItem);
|
this.onClose.emit(this.cardItem);
|
||||||
}, d);
|
}, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makePassage(p: string) {
|
||||||
|
return new BibleReference(
|
||||||
|
BibleReference.bookName(parseInt(p.split(':')[0], 10)).name +
|
||||||
|
' ' +
|
||||||
|
p.split(':')[1] +
|
||||||
|
':' +
|
||||||
|
p.split(':')[2]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
22
app/db/src/app/common/subscriber.component.ts
Normal file
22
app/db/src/app/common/subscriber.component.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { OnDestroy, Injectable } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SubscriberComponent implements OnDestroy {
|
||||||
|
protected subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
public ngOnDestroy(): void {
|
||||||
|
for (const subscription of this.subscriptions) {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addSubscriptions(...subs: Subscription[]) {
|
||||||
|
this.subscriptions.push(...subs);
|
||||||
|
}
|
||||||
|
protected addSubscription(sub: Subscription) {
|
||||||
|
this.subscriptions.push(sub);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ export interface AppState {
|
|||||||
readonly savedPages: readonly SavedPage[];
|
readonly savedPages: readonly SavedPage[];
|
||||||
readonly mainPages: readonly Page[];
|
readonly mainPages: readonly Page[];
|
||||||
readonly cards: readonly CardItem[];
|
readonly cards: readonly CardItem[];
|
||||||
|
readonly autocomplete: readonly string[];
|
||||||
readonly error: Error;
|
readonly error: Error;
|
||||||
readonly paragraphs: HashTable<Paragraph>;
|
readonly paragraphs: HashTable<Paragraph>;
|
||||||
readonly displaySettings: DisplaySettings;
|
readonly displaySettings: DisplaySettings;
|
||||||
@ -11,7 +12,7 @@ export interface Error {
|
|||||||
readonly msg: string;
|
readonly msg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Data = BiblePassageResult | StrongsResult;
|
export type Data = BiblePassageResult | StrongsResult | WordLookupResult;
|
||||||
|
|
||||||
export interface DisplaySettings {
|
export interface DisplaySettings {
|
||||||
readonly showStrongsAsModal: boolean;
|
readonly showStrongsAsModal: boolean;
|
||||||
@ -151,3 +152,22 @@ export interface RMACCrossReference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region Word Search
|
||||||
|
|
||||||
|
export interface WordLookupResult {
|
||||||
|
readonly refs: readonly string[];
|
||||||
|
readonly word: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IndexResult {
|
||||||
|
readonly r: readonly string[];
|
||||||
|
readonly w: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WordToStem {
|
||||||
|
readonly w: string;
|
||||||
|
readonly s: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
@ -152,7 +152,7 @@ export class PassageComponent extends CardComponent implements OnInit {
|
|||||||
const dict = this.cardItem.dict === 'H' ? 'heb' : 'grk';
|
const dict = this.cardItem.dict === 'H' ? 'heb' : 'grk';
|
||||||
const numbers = q.split(' ');
|
const numbers = q.split(' ');
|
||||||
for (const sn of numbers) {
|
for (const sn of numbers) {
|
||||||
this.appService.getNewStrongs(sn, dict, this.cardItem);
|
this.appService.getStrongs(sn, dict, this.cardItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
matInput
|
matInput
|
||||||
|
#autoCompleteInput
|
||||||
[formControl]="searchControl"
|
[formControl]="searchControl"
|
||||||
[matAutocomplete]="auto"
|
[matAutocomplete]="auto"
|
||||||
(keyup.enter)="search($event.target.value)"
|
(keyup.enter)="search($event.target.value)"
|
||||||
@ -34,8 +35,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
<div class="search-content">
|
<div class="search-content">
|
||||||
<ng-template [ngIf]="(cards$ | async).length > 0" [ngIfElse]="nocards">
|
<ng-container *ngIf="cards$ | async as cards; else nocards">
|
||||||
<mat-card *ngFor="let item of cards$ | async">
|
<mat-card *ngFor="let item of cards">
|
||||||
<app-passage
|
<app-passage
|
||||||
*ngIf="isPassage(item)"
|
*ngIf="isPassage(item)"
|
||||||
[cardItem]="item"
|
[cardItem]="item"
|
||||||
@ -48,8 +49,14 @@
|
|||||||
(onClose)="removeCard(item)"
|
(onClose)="removeCard(item)"
|
||||||
(onItemClicked)="getItemsNextToCard($event)"
|
(onItemClicked)="getItemsNextToCard($event)"
|
||||||
></app-strongs>
|
></app-strongs>
|
||||||
|
<app-words
|
||||||
|
*ngIf="isWords(item)"
|
||||||
|
[cardItem]="item"
|
||||||
|
(onClose)="removeCard(item)"
|
||||||
|
(onItemClicked)="getItemsNextToCard($event)"
|
||||||
|
></app-words>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</ng-template>
|
</ng-container>
|
||||||
<ng-template #nocards>
|
<ng-template #nocards>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,32 +1,43 @@
|
|||||||
import { Component, OnInit, HostListener } from '@angular/core';
|
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { AppService } from 'src/app/services/app.service';
|
import { AppService } from 'src/app/services/app.service';
|
||||||
import { NavService } from 'src/app/services/nav.service';
|
import { NavService } from 'src/app/services/nav.service';
|
||||||
import { OpenData, CardItem } from 'src/app/models/app-state';
|
import { OpenData, CardItem } from 'src/app/models/app-state';
|
||||||
import { BibleReference } from 'src/app/common/bible-reference';
|
import { BibleReference } from 'src/app/common/bible-reference';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { VersePickerModalComponent } from '../verse-picker/verse-picker-modal.component';
|
import { VersePickerModalComponent } from '../verse-picker/verse-picker-modal.component';
|
||||||
|
import { SubscriberComponent } from '../../../common/subscriber.component';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MatAutocompleteTrigger,
|
||||||
|
MatAutocomplete,
|
||||||
|
} from '@angular/material/autocomplete';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-search-page',
|
selector: 'app-search-page',
|
||||||
templateUrl: './search.page.html',
|
templateUrl: './search.page.html',
|
||||||
styleUrls: ['./search.page.scss'],
|
styleUrls: ['./search.page.scss'],
|
||||||
})
|
})
|
||||||
export class SearchPage implements OnInit {
|
export class SearchPage extends SubscriberComponent
|
||||||
|
implements OnInit, AfterViewInit {
|
||||||
cards$ = this.appService.select((state) => state.cards);
|
cards$ = this.appService.select((state) => state.cards);
|
||||||
suggestions$: Observable<string[]>;
|
suggestions$ = this.appService.select((state) => state.autocomplete);
|
||||||
|
|
||||||
searchControl = new FormControl();
|
searchControl = new FormControl();
|
||||||
|
|
||||||
|
@ViewChild(MatAutocomplete)
|
||||||
|
autoComplete: MatAutocomplete;
|
||||||
|
@ViewChild('autoCompleteInput', { read: MatAutocompleteTrigger })
|
||||||
|
autoCompleteTrigger: MatAutocompleteTrigger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private appService: AppService,
|
private appService: AppService,
|
||||||
public navService: NavService,
|
public navService: NavService,
|
||||||
public dialog: MatDialog
|
public dialog: MatDialog
|
||||||
) {}
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
// if a route was passed in, perform a search.
|
// if a route was passed in, perform a search.
|
||||||
@ -35,9 +46,25 @@ export class SearchPage implements OnInit {
|
|||||||
this.search(term);
|
this.search(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.suggestions$ = this.searchControl.valueChanges.pipe(
|
// subscribe to autocomplete input control's changes
|
||||||
map((value) => this.getSearchItems(value))
|
this.addSubscription(
|
||||||
|
this.searchControl.valueChanges.subscribe((value: string) =>
|
||||||
|
this.appService.getAutoComplete(value.toLowerCase())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.cards$.subscribe((state) => {
|
||||||
|
console.log('Cards updated...');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
// this.autoComplete.opened.subscribe((v) => {
|
||||||
|
// if (this.searchControl.value === '') {
|
||||||
|
// // close autocompet if the search control is empty...
|
||||||
|
// this.autoCompleteTrigger.closePanel();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
launchPicker() {
|
launchPicker() {
|
||||||
@ -55,6 +82,9 @@ export class SearchPage implements OnInit {
|
|||||||
async search(search: string) {
|
async search(search: string) {
|
||||||
// clear search box.
|
// clear search box.
|
||||||
this.searchControl.setValue('');
|
this.searchControl.setValue('');
|
||||||
|
if (this.autoCompleteTrigger) {
|
||||||
|
this.autoCompleteTrigger.closePanel();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const terms = search.split(';');
|
const terms = search.split(';');
|
||||||
@ -70,17 +100,17 @@ export class SearchPage implements OnInit {
|
|||||||
// });
|
// });
|
||||||
} else if (q.search(/[0-9]/i) === -1) {
|
} else if (q.search(/[0-9]/i) === -1) {
|
||||||
// // its a search term.
|
// // its a search term.
|
||||||
// list.push({ qry: q, dict: 'na', type: 'Words' });
|
await this.appService.getWords(q);
|
||||||
} else if (q.search(/(H|G)[0-9]/i) !== -1) {
|
} else if (q.search(/(H|G)[0-9]/i) !== -1) {
|
||||||
// its a strongs lookup
|
// its a strongs lookup
|
||||||
const dict = q.substring(0, 1).search(/h/i) !== -1 ? 'heb' : 'grk';
|
const dict = q.substring(0, 1).search(/h/i) !== -1 ? 'heb' : 'grk';
|
||||||
const strongsNumber = q.substring(1, q.length);
|
const strongsNumber = q.substring(1, q.length);
|
||||||
this.appService.getNewStrongs(strongsNumber, dict);
|
await this.appService.getStrongs(strongsNumber, dict);
|
||||||
} else {
|
} else {
|
||||||
// its a verse reference.
|
// its a verse reference.
|
||||||
if (q !== '') {
|
if (q !== '') {
|
||||||
const myref = new BibleReference(q.trim());
|
const myref = new BibleReference(q.trim());
|
||||||
this.appService.getNewPassage(myref);
|
await this.appService.getPassage(myref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,28 +139,10 @@ export class SearchPage implements OnInit {
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region Typeahead/Autocomplete
|
//#region Typeahead/Autocomplete
|
||||||
private getSearchItems(value: string): string[] {
|
|
||||||
const filterValue = value.toLowerCase();
|
|
||||||
|
|
||||||
return ['One', 'Two', 'Three'].filter((option) =>
|
|
||||||
option.toLowerCase().includes(filterValue)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
select(selection: any): void {
|
select(selection: any): void {
|
||||||
this.search(selection);
|
this.search(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click', ['$event'])
|
|
||||||
private documentClickHandler(event) {
|
|
||||||
// if (this.searchbarElem) {
|
|
||||||
// this.searchbarElem.getInputElement().then((el) => {
|
|
||||||
// if (el.contains(event.target)) {
|
|
||||||
// this.showList = false;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="card-title passage-title">
|
<div class="card-title strongs-title">
|
||||||
<mat-icon aria-hidden="false" aria-label="Strongs Entry Icon"
|
<mat-icon aria-hidden="false" aria-label="Strongs Entry Icon"
|
||||||
>article</mat-icon
|
>article</mat-icon
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.passage-title {
|
.strongs-title {
|
||||||
background-color: var(--strongs-color-primary);
|
background-color: var(--strongs-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||||
import { BibleReference } from '../../../common/bible-reference';
|
|
||||||
import { AppService } from '../../../services/app.service';
|
import { AppService } from '../../../services/app.service';
|
||||||
import { CardComponent } from '../../../common/card.component';
|
import { CardComponent } from '../../../common/card.component';
|
||||||
|
|
||||||
@ -33,18 +32,8 @@ export class StrongsComponent extends CardComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makePassage(p: string) {
|
|
||||||
return new BibleReference(
|
|
||||||
BibleReference.bookName(parseInt(p.split(';')[0], 10)).name +
|
|
||||||
' ' +
|
|
||||||
p.split(';')[1] +
|
|
||||||
':' +
|
|
||||||
p.split(';')[2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
openPassage(p: string) {
|
openPassage(p: string) {
|
||||||
const ref = this.makePassage(p);
|
const ref = this.makePassage(p);
|
||||||
this.appService.getNewPassage(ref, this.cardItem);
|
this.appService.getPassage(ref, this.cardItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ export class VersePickerModalComponent {
|
|||||||
|
|
||||||
setChapter(chapter: number) {
|
setChapter(chapter: number) {
|
||||||
// close the control, trigger the passage event.
|
// close the control, trigger the passage event.
|
||||||
this.appService.getNewPassage(
|
this.appService.getPassage(
|
||||||
new BibleReference(this.book.name + ' ' + chapter)
|
new BibleReference(this.book.name + ' ' + chapter)
|
||||||
);
|
);
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
|
47
app/db/src/app/search/components/words/words.component.html
Normal file
47
app/db/src/app/search/components/words/words.component.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<div class="card-title words-title">
|
||||||
|
<mat-icon aria-hidden="false" aria-label="Word Search Entry Icon"
|
||||||
|
>font_download</mat-icon
|
||||||
|
>
|
||||||
|
<span *ngIf="cardItem">{{ cardItem.qry }}</span>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
class="card-close-button"
|
||||||
|
aria-label="Remove the word search card from the list"
|
||||||
|
(click)="close($event)"
|
||||||
|
>
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-content" *ngIf="cardItem" #words>
|
||||||
|
<span *ngFor="let ref of cardItem.data.refs" class="passage-button-wrapper"
|
||||||
|
><a class="passage-button" (click)="openPassage(ref)">{{
|
||||||
|
makePassage(ref)
|
||||||
|
}}</a></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<span class="card-actions-left">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Remove the passage card from the list"
|
||||||
|
(click)="close($event)"
|
||||||
|
>
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
<span class="card-actions-right">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matMenuTriggerFor]="moreMenu"
|
||||||
|
aria-label="Example icon-button with a menu"
|
||||||
|
>
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #moreMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="copy()">
|
||||||
|
<mat-icon>content_copy</mat-icon>
|
||||||
|
<span>Copy Passage</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
|
</span>
|
||||||
|
</div>
|
43
app/db/src/app/search/components/words/words.component.scss
Normal file
43
app/db/src/app/search/components/words/words.component.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.words-title {
|
||||||
|
background-color: var(--words-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-close-button {
|
||||||
|
color: var(--words-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
color: var(--words-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 25rem;
|
||||||
|
font-family: var(--card-font);
|
||||||
|
font-size: var(--font-size);
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
.passage-button-wrapper {
|
||||||
|
width: 33.3%;
|
||||||
|
min-width: 13rem;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.passage-button {
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0.3rem;
|
||||||
|
background-color: var(--words-color-button);
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.passage-button:hover {
|
||||||
|
background-color: var(--words-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 687px) {
|
||||||
|
.passage-button-wrapper {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
40
app/db/src/app/search/components/words/words.component.ts
Normal file
40
app/db/src/app/search/components/words/words.component.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||||
|
import { AppService } from '../../../services/app.service';
|
||||||
|
import { CardComponent } from '../../../common/card.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-words',
|
||||||
|
templateUrl: 'words.component.html',
|
||||||
|
styleUrls: ['./words.component.scss'],
|
||||||
|
preserveWhitespaces: true,
|
||||||
|
})
|
||||||
|
export class WordsComponent extends CardComponent {
|
||||||
|
@ViewChild('words') wordsElement;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected elementRef: ElementRef,
|
||||||
|
private appService: AppService
|
||||||
|
) {
|
||||||
|
super(elementRef);
|
||||||
|
console.log('rendering word search...');
|
||||||
|
}
|
||||||
|
|
||||||
|
copy() {
|
||||||
|
const html = this.wordsElement.nativeElement.innerHTML;
|
||||||
|
const text = this.wordsElement.nativeElement.innerText;
|
||||||
|
this.copyToClip(text, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
openItem(p: string) {
|
||||||
|
this.onItemClicked.emit({
|
||||||
|
card: this.cardItem,
|
||||||
|
qry: p,
|
||||||
|
from_search_bar: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openPassage(p: string) {
|
||||||
|
const ref = this.makePassage(p);
|
||||||
|
this.appService.getPassage(ref, this.cardItem);
|
||||||
|
}
|
||||||
|
}
|
@ -7,17 +7,18 @@ import {
|
|||||||
Paragraph,
|
Paragraph,
|
||||||
BiblePassage,
|
BiblePassage,
|
||||||
BibleVerse,
|
BibleVerse,
|
||||||
Data,
|
|
||||||
Error,
|
Error,
|
||||||
BibleParagraph,
|
BibleParagraph,
|
||||||
BibleParagraphPassage,
|
BibleParagraphPassage,
|
||||||
CardItem,
|
CardItem,
|
||||||
DictionaryType,
|
DictionaryType,
|
||||||
StrongsResult,
|
|
||||||
StrongsDefinition,
|
StrongsDefinition,
|
||||||
StrongsCrossReference,
|
StrongsCrossReference,
|
||||||
RMACCrossReference,
|
RMACCrossReference,
|
||||||
RMACDefinition,
|
RMACDefinition,
|
||||||
|
WordLookupResult,
|
||||||
|
WordToStem,
|
||||||
|
IndexResult,
|
||||||
} from '../models/app-state';
|
} from '../models/app-state';
|
||||||
import { Section, BibleReference } from '../common/bible-reference';
|
import { Section, BibleReference } from '../common/bible-reference';
|
||||||
import { PageTitles } from '../constants';
|
import { PageTitles } from '../constants';
|
||||||
@ -26,6 +27,7 @@ import * as math from 'mathjs';
|
|||||||
|
|
||||||
const initialState: AppState = {
|
const initialState: AppState = {
|
||||||
cards: [],
|
cards: [],
|
||||||
|
autocomplete: [],
|
||||||
savedPages: [],
|
savedPages: [],
|
||||||
mainPages: [
|
mainPages: [
|
||||||
{ title: PageTitles.Search, icon: 'search' },
|
{ title: PageTitles.Search, icon: 'search' },
|
||||||
@ -81,6 +83,10 @@ type AppAction =
|
|||||||
| {
|
| {
|
||||||
type: 'UPDATE_FONT_FAMILY';
|
type: 'UPDATE_FONT_FAMILY';
|
||||||
family: string;
|
family: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'UPDATE_AUTOCOMPLETE';
|
||||||
|
words: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function reducer(state: AppState, action: AppAction): AppState {
|
function reducer(state: AppState, action: AppAction): AppState {
|
||||||
@ -90,6 +96,12 @@ function reducer(state: AppState, action: AppAction): AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'UPDATE_AUTOCOMPLETE': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
autocomplete: [...action.words],
|
||||||
|
};
|
||||||
|
}
|
||||||
case 'UPDATE_PAGES': {
|
case 'UPDATE_PAGES': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -173,9 +185,17 @@ function reducer(state: AppState, action: AppAction): AppState {
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AppService extends createStateService(reducer, initialState) {
|
export class AppService extends createStateService(reducer, initialState) {
|
||||||
|
private wordToStem: Map<string, string>;
|
||||||
|
|
||||||
|
private searchIndexArray: string[];
|
||||||
|
private autocomplete: string[];
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.searchIndexArray = this.buildIndexArray().sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSavedPages() {
|
async getSavedPages() {
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'UPDATE_PAGES',
|
type: 'UPDATE_PAGES',
|
||||||
@ -188,17 +208,6 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getParagraphMarkers(): Promise<HashTable<Paragraph>> {
|
|
||||||
const paras = await this.http
|
|
||||||
.get<HashTable<Paragraph>>('assets/data/bibles/paras.json')
|
|
||||||
.toPromise();
|
|
||||||
this.dispatch({
|
|
||||||
type: 'UPDATE_PARAGRAPHS',
|
|
||||||
paragraphs: paras,
|
|
||||||
});
|
|
||||||
return paras;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCard(card: CardItem) {
|
removeCard(card: CardItem) {
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'REMOVE_CARD',
|
type: 'REMOVE_CARD',
|
||||||
@ -206,9 +215,26 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private dispatchError(msg: string) {
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_ERROR',
|
||||||
|
error: {
|
||||||
|
msg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatReferenceKey(
|
||||||
|
book: number | string,
|
||||||
|
chapter: number | string,
|
||||||
|
vs: number | string
|
||||||
|
) {
|
||||||
|
return `${book}:${chapter}:${vs}`;
|
||||||
|
}
|
||||||
//#region Strongs
|
//#region Strongs
|
||||||
|
|
||||||
async getNewStrongs(
|
async getStrongs(
|
||||||
strongsNumber: string,
|
strongsNumber: string,
|
||||||
dict: DictionaryType,
|
dict: DictionaryType,
|
||||||
nextToItem: CardItem = null
|
nextToItem: CardItem = null
|
||||||
@ -244,23 +270,17 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
if (dict === 'grk') {
|
if (dict === 'grk') {
|
||||||
result.prefix = 'G';
|
result.prefix = 'G';
|
||||||
if (sn > 5624 || sn < 1) {
|
if (sn > 5624 || sn < 1) {
|
||||||
this.dispatch({
|
this.dispatchError(
|
||||||
type: 'UPDATE_ERROR',
|
`Strong's Number G${sn} is out of range. Strong's numbers range from 1 - 5624 in the New Testament.`
|
||||||
error: {
|
);
|
||||||
msg: `Strong's Number G${sn} is out of range. Strong's numbers range from 1 - 5624 in the New Testament.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.prefix = 'H';
|
result.prefix = 'H';
|
||||||
if (sn > 8674 || sn < 1) {
|
if (sn > 8674 || sn < 1) {
|
||||||
this.dispatch({
|
this.dispatchError(
|
||||||
type: 'UPDATE_ERROR',
|
`Strong's Number H${sn} is out of range. Strong's numbers range from 1 - 8674 in the Old Testament.`
|
||||||
error: {
|
);
|
||||||
msg: `Strong's Number H${sn} is out of range. Strong's numbers range from 1 - 8674 in the Old Testament.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,12 +292,9 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
.toPromise();
|
.toPromise();
|
||||||
result.def = d.find((el) => el.i === result.prefix + result.sn);
|
result.def = d.find((el) => el.i === result.prefix + result.sn);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dispatch({
|
this.dispatchError(
|
||||||
type: 'UPDATE_ERROR',
|
`Unable to retrieve Strong's Data for ${result.prefix}${result.sn}`
|
||||||
error: {
|
);
|
||||||
msg: `Unable to retrieve Strong's Data for ${result.prefix}${result.sn}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,12 +309,10 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dispatch({
|
this.dispatchError(
|
||||||
type: 'UPDATE_ERROR',
|
`Unable to retrieve Strong\'s Cross References for ${result.prefix}${result.sn}`
|
||||||
error: {
|
);
|
||||||
msg: `Unable to retrieve Strong\'s Cross References for ${result.prefix}${result.sn}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,12 +326,7 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
const d = await this.http.get<RMACCrossReference[]>(url).toPromise();
|
const d = await this.http.get<RMACCrossReference[]>(url).toPromise();
|
||||||
rmacCrossReferences = d;
|
rmacCrossReferences = d;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dispatch({
|
this.dispatchError('Unable to retrieve RMAC');
|
||||||
type: 'UPDATE_ERROR',
|
|
||||||
error: {
|
|
||||||
msg: 'Unable to retrieve RMAC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,12 +352,7 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dispatch({
|
this.dispatchError('Unable to retrieve RMAC');
|
||||||
type: 'UPDATE_ERROR',
|
|
||||||
error: {
|
|
||||||
msg: 'Unable to retrieve RMAC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -358,7 +363,7 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
|
|
||||||
//#region Bible Passages
|
//#region Bible Passages
|
||||||
|
|
||||||
async getNewPassage(ref: BibleReference, nextToItem: CardItem = null) {
|
async getPassage(ref: BibleReference, nextToItem: CardItem = null) {
|
||||||
const card = await this.composeBiblePassageCardItem(ref);
|
const card = await this.composeBiblePassageCardItem(ref);
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'ADD_CARD',
|
type: 'ADD_CARD',
|
||||||
@ -396,22 +401,16 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (Number(section.start.chapter) > section.start.book.last_chapter) {
|
if (Number(section.start.chapter) > section.start.book.last_chapter) {
|
||||||
this.dispatch({
|
this.dispatchError(
|
||||||
type: 'UPDATE_ERROR',
|
`The requested chapter ${section.start.book.name} is out of range. Please pick a chapter between 1 and ${section.end.book.last_chapter}.`
|
||||||
error: {
|
);
|
||||||
msg: `The requested chapter ${section.start.book.name} is out of range. Please pick a chapter between 1 and ${section.end.book.last_chapter}.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number(section.end.chapter) > section.end.book.last_chapter) {
|
if (Number(section.end.chapter) > section.end.book.last_chapter) {
|
||||||
this.dispatch({
|
this.dispatchError(
|
||||||
type: 'UPDATE_ERROR',
|
`The requested chapter ${section.end.book.name} is out of range. Please pick a chapter between 1 and ${section.end.book.last_chapter}.`
|
||||||
error: {
|
);
|
||||||
msg: `The requested chapter ${section.end.book.name} is out of range. Please pick a chapter between 1 and ${section.end.book.last_chapter}.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,12 +427,7 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
.toPromise();
|
.toPromise();
|
||||||
chapters.push(d);
|
chapters.push(d);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dispatch({
|
this.dispatchError(`Unable to retrieve bible passage ${result.ref}.`);
|
||||||
type: 'UPDATE_ERROR',
|
|
||||||
error: {
|
|
||||||
msg: `Unable to retrieve bible passage ${result.ref}.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -503,13 +497,7 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dispatch({
|
this.dispatchError(`An unknown error occurred: ${error}.`);
|
||||||
type: 'UPDATE_ERROR',
|
|
||||||
error: {
|
|
||||||
msg: `An unknown error occurred: ${error}.`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,6 +526,17 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
return passages;
|
return passages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getParagraphMarkers(): Promise<HashTable<Paragraph>> {
|
||||||
|
const paras = await this.http
|
||||||
|
.get<HashTable<Paragraph>>('assets/data/bibles/paras.json')
|
||||||
|
.toPromise();
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_PARAGRAPHS',
|
||||||
|
paragraphs: paras,
|
||||||
|
});
|
||||||
|
return paras;
|
||||||
|
}
|
||||||
|
|
||||||
private convertToParagraphs(
|
private convertToParagraphs(
|
||||||
ch: BiblePassage,
|
ch: BiblePassage,
|
||||||
section: Section,
|
section: Section,
|
||||||
@ -576,10 +575,534 @@ export class AppService extends createStateService(reducer, initialState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getRefKey(vs: BibleVerse, section: Section) {
|
private getRefKey(vs: BibleVerse, section: Section) {
|
||||||
return (
|
return this.formatReferenceKey(
|
||||||
section.start.book.book_number + ';' + section.start.chapter + ';' + vs.v
|
section.start.book.book_number,
|
||||||
|
section.start.chapter,
|
||||||
|
vs.v
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region Word Search
|
||||||
|
|
||||||
|
async getWords(qry: string, nextToItem: CardItem = null) {
|
||||||
|
const result = await this.getWordsFromApi(qry);
|
||||||
|
|
||||||
|
const card = {
|
||||||
|
qry,
|
||||||
|
dict: 'n/a',
|
||||||
|
type: 'Words',
|
||||||
|
data: result,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dispatch({
|
||||||
|
type: 'ADD_CARD',
|
||||||
|
card,
|
||||||
|
nextToItem,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getWordsFromApi(qry: string): Promise<WordLookupResult> {
|
||||||
|
// its possible that this might get called before the word to stem map is build. first time, check and populate...
|
||||||
|
if (!this.wordToStem) {
|
||||||
|
await this.getStemWordIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// now carry on...
|
||||||
|
const qs = this.normalizeQueryString(qry);
|
||||||
|
const results: (readonly string[])[] = [];
|
||||||
|
|
||||||
|
// Loop through each query term.
|
||||||
|
for (const q of qs) {
|
||||||
|
if (!this.wordToStem.has(q)) {
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_ERROR',
|
||||||
|
error: {
|
||||||
|
msg: `Unable to find the word "${q}" in any passages.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const stem = this.wordToStem.get(q);
|
||||||
|
|
||||||
|
// handle the first case.
|
||||||
|
if (stem <= this.searchIndexArray[0]) {
|
||||||
|
results.unshift(
|
||||||
|
await this.getSearchReferences(
|
||||||
|
'assets/data/index/' + this.searchIndexArray[0] + 'idx.json',
|
||||||
|
stem
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each query term, figure out which xml file it is in, and get it.
|
||||||
|
// getSearchRefs returns an array of references.
|
||||||
|
for (let w = 1; w < this.searchIndexArray.length; w++) {
|
||||||
|
// If we are at the end of the array, we want to use a different test.
|
||||||
|
if (
|
||||||
|
stem <= this.searchIndexArray[w] &&
|
||||||
|
stem > this.searchIndexArray[w - 1]
|
||||||
|
) {
|
||||||
|
results.unshift(
|
||||||
|
await this.getSearchReferences(
|
||||||
|
'assets/data/index/' + this.searchIndexArray[w] + 'idx.json',
|
||||||
|
stem
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // End of loop through query terms
|
||||||
|
|
||||||
|
// Now we need to test results. If there is more than one item in the array, we need to find the set
|
||||||
|
// that is shared by all of them. IF not, we can just return those refs.
|
||||||
|
if (results.length === 0) {
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_ERROR',
|
||||||
|
error: {
|
||||||
|
msg: `No passages found for query: ${qry}.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.length === 1) {
|
||||||
|
// we go down this path if only one word was searched for
|
||||||
|
return {
|
||||||
|
refs: results[0],
|
||||||
|
word: qry,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// we go down this path if more than one word was searched for
|
||||||
|
return {
|
||||||
|
refs: this.findSharedSet(results),
|
||||||
|
word: qry,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the references a given word is found in.
|
||||||
|
* Returns a string[].
|
||||||
|
* @param url - The url of the word index
|
||||||
|
* @param query - The word to lookup.
|
||||||
|
*/
|
||||||
|
private async getStemWordIndex() {
|
||||||
|
this.wordToStem = new Map<string, string>();
|
||||||
|
try {
|
||||||
|
const r = await this.http
|
||||||
|
.get<WordToStem[]>('assets/data/index/word_to_stem_idx.json')
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
// find the right word
|
||||||
|
for (const i of r) {
|
||||||
|
this.wordToStem.set(i.w, i.s);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_ERROR',
|
||||||
|
error: {
|
||||||
|
msg: err,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the references a given word is found in.
|
||||||
|
* Returns a string[].
|
||||||
|
* @param url - The url of the word index
|
||||||
|
* @param query - The word to lookup.
|
||||||
|
*/
|
||||||
|
private async getSearchReferences(url: string, query: string) {
|
||||||
|
// getSearchRefs takes a url and uses ajax to retrieve the references and returns an array of references.
|
||||||
|
let r: IndexResult[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
r = await this.http.get<IndexResult[]>(url).toPromise();
|
||||||
|
} catch (err) {
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_ERROR',
|
||||||
|
error: {
|
||||||
|
msg: err,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// find the right word
|
||||||
|
const refs = r.filter((o) => o.w === query);
|
||||||
|
|
||||||
|
if (refs.length > 0) {
|
||||||
|
return refs[0].r;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildIndexArray() {
|
||||||
|
const words: string[] = [];
|
||||||
|
words.unshift('abishur');
|
||||||
|
words.unshift('achor');
|
||||||
|
words.unshift('adoni');
|
||||||
|
words.unshift('afterward');
|
||||||
|
words.unshift('ahishahar');
|
||||||
|
words.unshift('alleg');
|
||||||
|
words.unshift('ambush');
|
||||||
|
words.unshift('ancestor');
|
||||||
|
words.unshift('aphik');
|
||||||
|
words.unshift('arbah');
|
||||||
|
words.unshift('arodi');
|
||||||
|
words.unshift('ashkenaz');
|
||||||
|
words.unshift('ate');
|
||||||
|
words.unshift('azaniah');
|
||||||
|
words.unshift('backbiteth');
|
||||||
|
words.unshift('barbarian');
|
||||||
|
words.unshift('beard');
|
||||||
|
words.unshift('begettest');
|
||||||
|
words.unshift('benefactor');
|
||||||
|
words.unshift('bethel');
|
||||||
|
words.unshift('bilshan');
|
||||||
|
words.unshift('blindeth');
|
||||||
|
words.unshift('booti');
|
||||||
|
words.unshift('breaketh');
|
||||||
|
words.unshift('bucket');
|
||||||
|
words.unshift('cabbon');
|
||||||
|
words.unshift('caphtor');
|
||||||
|
words.unshift('causeless');
|
||||||
|
words.unshift('chapmen');
|
||||||
|
words.unshift('chese');
|
||||||
|
words.unshift('chrysoprasus');
|
||||||
|
words.unshift('cloth');
|
||||||
|
words.unshift('common');
|
||||||
|
words.unshift('confess');
|
||||||
|
words.unshift('contendeth');
|
||||||
|
words.unshift('coucheth');
|
||||||
|
words.unshift('crept');
|
||||||
|
words.unshift('curseth');
|
||||||
|
words.unshift('darius');
|
||||||
|
words.unshift('decketh');
|
||||||
|
words.unshift('dema');
|
||||||
|
words.unshift('devil');
|
||||||
|
words.unshift('directeth');
|
||||||
|
words.unshift('disposit');
|
||||||
|
words.unshift('doth');
|
||||||
|
words.unshift('drowsi');
|
||||||
|
words.unshift('ebe');
|
||||||
|
words.unshift('elead');
|
||||||
|
words.unshift('elkoshit');
|
||||||
|
words.unshift('encourag');
|
||||||
|
words.unshift('entreat');
|
||||||
|
words.unshift('eschew');
|
||||||
|
words.unshift('ever');
|
||||||
|
words.unshift('expert');
|
||||||
|
words.unshift('fallest');
|
||||||
|
words.unshift('feedeth');
|
||||||
|
words.unshift('filthi');
|
||||||
|
words.unshift('fleeth');
|
||||||
|
words.unshift('forborn');
|
||||||
|
words.unshift('forsookest');
|
||||||
|
words.unshift('fretteth');
|
||||||
|
words.unshift('gahar');
|
||||||
|
words.unshift('gazzam');
|
||||||
|
words.unshift('gibea');
|
||||||
|
words.unshift('glister');
|
||||||
|
words.unshift('got');
|
||||||
|
words.unshift('grope');
|
||||||
|
words.unshift('hadlai');
|
||||||
|
words.unshift('hammon');
|
||||||
|
words.unshift('harbona');
|
||||||
|
words.unshift('hasrah');
|
||||||
|
words.unshift('hazezon');
|
||||||
|
words.unshift('heinous');
|
||||||
|
words.unshift('herebi');
|
||||||
|
words.unshift('highest');
|
||||||
|
words.unshift('holdeth');
|
||||||
|
words.unshift('hosanna');
|
||||||
|
words.unshift('huri');
|
||||||
|
words.unshift('ill');
|
||||||
|
words.unshift('inexcus');
|
||||||
|
words.unshift('intend');
|
||||||
|
words.unshift('ishui');
|
||||||
|
words.unshift('jaazaniah');
|
||||||
|
words.unshift('jaminit');
|
||||||
|
words.unshift('jecoliah');
|
||||||
|
words.unshift('jeopard');
|
||||||
|
words.unshift('jethro');
|
||||||
|
words.unshift('joiarib');
|
||||||
|
words.unshift('juda');
|
||||||
|
words.unshift('kelaiah');
|
||||||
|
words.unshift('kishion');
|
||||||
|
words.unshift('laden');
|
||||||
|
words.unshift('laughter');
|
||||||
|
words.unshift('lehi');
|
||||||
|
words.unshift('lift');
|
||||||
|
words.unshift('loatheth');
|
||||||
|
words.unshift('lucius');
|
||||||
|
words.unshift('madmen');
|
||||||
|
words.unshift('malachi');
|
||||||
|
words.unshift('march');
|
||||||
|
words.unshift('maul');
|
||||||
|
words.unshift('melchizedek');
|
||||||
|
words.unshift('merrili');
|
||||||
|
words.unshift('midianit');
|
||||||
|
words.unshift('miri');
|
||||||
|
words.unshift('modest');
|
||||||
|
words.unshift('move');
|
||||||
|
words.unshift('naashon');
|
||||||
|
words.unshift('nazareth');
|
||||||
|
words.unshift('nephishesim');
|
||||||
|
words.unshift('nisan');
|
||||||
|
words.unshift('obadiah');
|
||||||
|
words.unshift('oliveyard');
|
||||||
|
words.unshift('oren');
|
||||||
|
words.unshift('overrun');
|
||||||
|
words.unshift('pallu');
|
||||||
|
words.unshift('pas');
|
||||||
|
words.unshift('peel');
|
||||||
|
words.unshift('pernici');
|
||||||
|
words.unshift('philip');
|
||||||
|
words.unshift('pison');
|
||||||
|
words.unshift('plucketh');
|
||||||
|
words.unshift('pour');
|
||||||
|
words.unshift('price');
|
||||||
|
words.unshift('proport');
|
||||||
|
words.unshift('purg');
|
||||||
|
words.unshift('rabboni');
|
||||||
|
words.unshift('ravish');
|
||||||
|
words.unshift('redeemedst');
|
||||||
|
words.unshift('remainest');
|
||||||
|
words.unshift('reput');
|
||||||
|
words.unshift('revers');
|
||||||
|
words.unshift('rissah');
|
||||||
|
words.unshift('ruddi');
|
||||||
|
words.unshift('said');
|
||||||
|
words.unshift('sapphir');
|
||||||
|
words.unshift('scepter');
|
||||||
|
words.unshift('secundus');
|
||||||
|
words.unshift('separ');
|
||||||
|
words.unshift('shachia');
|
||||||
|
words.unshift('sharar');
|
||||||
|
words.unshift('sheepshear');
|
||||||
|
words.unshift('sheva');
|
||||||
|
words.unshift('shishak');
|
||||||
|
words.unshift('shroud');
|
||||||
|
words.unshift('signifi');
|
||||||
|
words.unshift('sittest');
|
||||||
|
words.unshift('slow');
|
||||||
|
words.unshift('soft');
|
||||||
|
words.unshift('sowedst');
|
||||||
|
words.unshift('spoil');
|
||||||
|
words.unshift('station');
|
||||||
|
words.unshift('stoop');
|
||||||
|
words.unshift('strongest');
|
||||||
|
words.unshift('sum');
|
||||||
|
words.unshift('sweep');
|
||||||
|
words.unshift('tahapan');
|
||||||
|
words.unshift('tast');
|
||||||
|
words.unshift('ten');
|
||||||
|
words.unshift('thereat');
|
||||||
|
words.unshift('threaten');
|
||||||
|
words.unshift('timbrel');
|
||||||
|
words.unshift('tongu');
|
||||||
|
words.unshift('travailest');
|
||||||
|
words.unshift('trust');
|
||||||
|
words.unshift('uncircumcis');
|
||||||
|
words.unshift('unprepar');
|
||||||
|
words.unshift('urg');
|
||||||
|
words.unshift('vat');
|
||||||
|
words.unshift('visiteth');
|
||||||
|
words.unshift('wash');
|
||||||
|
words.unshift('wed');
|
||||||
|
words.unshift('wherewith');
|
||||||
|
words.unshift('winepress');
|
||||||
|
words.unshift('won');
|
||||||
|
words.unshift('written');
|
||||||
|
words.unshift('zalmonah');
|
||||||
|
words.unshift('zenan');
|
||||||
|
words.unshift('ziphim');
|
||||||
|
words.unshift('zuzim');
|
||||||
|
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a list of references in string form as a string[] that are shared
|
||||||
|
* given a list of lists of references in string form.
|
||||||
|
*/
|
||||||
|
private findSharedSet(referenceSet: (readonly string[])[]) {
|
||||||
|
const results = [];
|
||||||
|
// FindSharedSet takes an array of reference arrays, and figures out
|
||||||
|
// which references are shared by all arrays/sets, then returns a single
|
||||||
|
// array of references.
|
||||||
|
// tslint:disable-next-line: prefer-for-of
|
||||||
|
for (let j = 0; j < referenceSet.length; j++) {
|
||||||
|
const refs = referenceSet[j];
|
||||||
|
if (refs != null) {
|
||||||
|
for (let i = 0; i < refs.length; i++) {
|
||||||
|
const r = refs[i].split(':');
|
||||||
|
// convert references to single integers.
|
||||||
|
// Book * 100000000, Chapter * 10000, Verse remains same, add all together.
|
||||||
|
results[j][i] = this.toReferenceNumber(r);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the first result
|
||||||
|
let first = results[0];
|
||||||
|
|
||||||
|
// for each additional result, get the shared set
|
||||||
|
for (let i = 1; i < results.length; i++) {
|
||||||
|
first = this.returnSharedSet(results[i], first);
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the references back into book, chapter and verse.
|
||||||
|
for (let i = 0; i < first.length; i++) {
|
||||||
|
const ref = first[i];
|
||||||
|
first[i] = this.toReferenceString(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toReferenceString(ref: number) {
|
||||||
|
return this.formatReferenceKey(
|
||||||
|
Math.floor(ref / 100000000),
|
||||||
|
Math.floor((ref % 100000000) / 10000),
|
||||||
|
Math.floor((ref % 100000000) % 10000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toReferenceNumber(r: string[]) {
|
||||||
|
let ref = parseInt(r[0], 10) * 100000000;
|
||||||
|
ref = ref + parseInt(r[1], 10) * 10000;
|
||||||
|
ref = ref + parseInt(r[2], 10) * 1;
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
private returnSharedSet(x, y) {
|
||||||
|
/// <summary>
|
||||||
|
/// Takes two javascript arrays and returns an array
|
||||||
|
/// containing a set of values shared by arrays.
|
||||||
|
/// </summary>
|
||||||
|
// declare iterator
|
||||||
|
let i = 0;
|
||||||
|
// declare terminator
|
||||||
|
let t = x.length < y.length ? x.length : y.length;
|
||||||
|
// sort the arrays
|
||||||
|
x.sort((a, b) => a - b);
|
||||||
|
y.sort((a, b) => a - b);
|
||||||
|
// in this loop, we remove from the arrays, the
|
||||||
|
// values that aren't shared between them.
|
||||||
|
while (i < t) {
|
||||||
|
if (x[i] === y[i]) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x[i] < y[i]) {
|
||||||
|
x.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x[i] > y[i]) {
|
||||||
|
y.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
t = x.length < y.length ? x.length : y.length;
|
||||||
|
// we have to make sure to remove any extra values
|
||||||
|
// at the end of an array when we reach the end of
|
||||||
|
// the other.
|
||||||
|
if (t === i && t < x.length) {
|
||||||
|
x.splice(i, x.length - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t === i && t < y.length) {
|
||||||
|
y.splice(i, x.length - i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we could return y, because at this time, both arrays
|
||||||
|
// are identical.
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeQueryString(qry: string): string[] {
|
||||||
|
qry = qry.toLowerCase();
|
||||||
|
return qry.replace(/'/g, '').replace(/\s+/g, ' ').split(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Auto Complete
|
||||||
|
|
||||||
|
async getAutoComplete(keyword: string) {
|
||||||
|
if (!this.autocomplete) {
|
||||||
|
// if you have't populated the word list yet, do so...
|
||||||
|
const data = await this.http
|
||||||
|
.get<WordToStem[]>('assets/data/index/word_to_stem_idx.json')
|
||||||
|
.toPromise();
|
||||||
|
this.autocomplete = data.map((o) => o.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
let qry = keyword;
|
||||||
|
let prefix = '';
|
||||||
|
const idx = qry.lastIndexOf(';');
|
||||||
|
const words: string[] = [];
|
||||||
|
|
||||||
|
if (idx > -1) {
|
||||||
|
qry = keyword.substr(idx + 1).trim();
|
||||||
|
prefix = keyword.substr(0, idx).trim() + '; ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qry.search(/[0-9]/i) === -1) {
|
||||||
|
// its a word
|
||||||
|
for (const item of BibleReference.Books) {
|
||||||
|
if (
|
||||||
|
item.name !== 'Unknown' &&
|
||||||
|
(item.name.toLowerCase().indexOf(qry.toLowerCase()) > -1 ||
|
||||||
|
item.short_name.toLowerCase().indexOf(qry.toLowerCase()) > -1)
|
||||||
|
) {
|
||||||
|
words.push(prefix + item.name);
|
||||||
|
if (words.length > 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of this.autocomplete) {
|
||||||
|
if (item.toLowerCase().indexOf(qry.toLowerCase()) > -1) {
|
||||||
|
words.push(prefix + item);
|
||||||
|
if (words.length > 6) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_AUTOCOMPLETE',
|
||||||
|
words,
|
||||||
|
});
|
||||||
|
} else if (qry.search(/(H|G)[0-9]/i) !== -1) {
|
||||||
|
// its a strongs lookup
|
||||||
|
if (qry.substr(0, 1).toUpperCase() === 'H') {
|
||||||
|
const num = parseInt(qry.substr(1), 10);
|
||||||
|
for (let x = num; x < num + 10 && x < 8675; x++) {
|
||||||
|
words.push('H' + x);
|
||||||
|
}
|
||||||
|
} else if (qry.substr(0, 1).toUpperCase() === 'G') {
|
||||||
|
const num = parseInt(qry.substr(1), 10);
|
||||||
|
for (let x = num; x < num + 10 && x < 5625; x++) {
|
||||||
|
words.push('G' + x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatch({
|
||||||
|
type: 'UPDATE_AUTOCOMPLETE',
|
||||||
|
words,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,11 @@ html {
|
|||||||
--strongs-color-primary: rgb(17, 70, 29);
|
--strongs-color-primary: rgb(17, 70, 29);
|
||||||
--strongs-color-accent: rgb(122, 206, 143);
|
--strongs-color-accent: rgb(122, 206, 143);
|
||||||
--strongs-heading-font-family: "Roboto Condensed";
|
--strongs-heading-font-family: "Roboto Condensed";
|
||||||
|
|
||||||
|
--words-color-primary: rgb(0, 85, 85);
|
||||||
|
--words-color-accent: rgb(9, 172, 172);
|
||||||
|
--words-color-button: rgb(27, 133, 133);
|
||||||
|
--words-heading-font-family: "Roboto Condensed";
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -38,8 +43,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
line-height: 100%;
|
line-height: 110%;
|
||||||
vertical-align: text-top;
|
vertical-align: top;
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user