add notes support

clean up some scss variables
add some settings
This commit is contained in:
Jason Wall 2020-08-02 15:56:23 -04:00
parent 1cd1fc7edf
commit 3df7d73161
26 changed files with 709 additions and 34 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
**/obj/**
**/bin/**
DynamicBibleUtility/packages/Shaman.ValueString.1.0.2.19/.signature.p7s
DynamicBibleUtility/packages/
DynamicBibleUtility/.vs/

123
app/db/package-lock.json generated
View File

@ -1567,6 +1567,14 @@
"webpack-sources": "1.4.3"
}
},
"@ngx-pwa/local-storage": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@ngx-pwa/local-storage/-/local-storage-10.0.1.tgz",
"integrity": "sha512-Tbp0GpqHD4SWhiJyVBlHwyx6hxZWHc/fA+wclhsmhYBah5+/sDFVla77gR62V1Yjx4vEHDi2fGVo0Ea6jbd+Fw==",
"requires": {
"tslib": "^2.0.0"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
@ -2132,6 +2140,11 @@
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
},
"angular2-uuid": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/angular2-uuid/-/angular2-uuid-1.1.1.tgz",
"integrity": "sha1-cvA81TK39AAy6x7PufhFc4S+lW4="
},
"ansi-colors": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@ -2628,6 +2641,25 @@
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
"dev": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
},
"dependencies": {
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
}
}
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
@ -3215,6 +3247,17 @@
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
"dev": true
},
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -3749,7 +3792,7 @@
"integrity": "sha1-PQeYpR5ziZxs5VnZN2Olb8eh2IM=",
"requires": {
"co": "^3",
"cogent": "git://github.com/timaschew/cogent.git#2246bd071392f5053a3a110024fd608a40a593ba",
"cogent": "git://github.com/timaschew/cogent.git#fix-redirects",
"component-consoler": "^2.0.0",
"component-validator": "^1.0.0",
"debug": "*",
@ -4945,6 +4988,12 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -6415,6 +6464,15 @@
"slash": "^3.0.0"
}
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"got": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/got/-/got-0.2.0.tgz",
@ -8447,6 +8505,11 @@
"object-visit": "^1.0.0"
}
},
"marked": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz",
"integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg=="
},
"mathjs": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-7.1.0.tgz",
@ -8835,6 +8898,13 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
"dev": true,
"optional": true
},
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@ -8894,6 +8964,23 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
},
"ngx-md": {
"version": "8.1.6",
"resolved": "https://registry.npmjs.org/ngx-md/-/ngx-md-8.1.6.tgz",
"integrity": "sha512-uecStRlxoyOXFeFBNPIsNZDpkOzApWiivQVH4OH/7FvUtIkZrS28dNnmzQvt076t4Fr2UJVxUe8h60dtI1BtuA==",
"requires": {
"marked": "^0.7.0",
"prismjs": "^1.15.0",
"tslib": "^1.9.0"
},
"dependencies": {
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
}
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -10652,6 +10739,14 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true
},
"prismjs": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz",
"integrity": "sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==",
"requires": {
"clipboard": "^2.0.0"
}
},
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@ -12026,6 +12121,12 @@
"resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz",
"integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ="
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@ -14156,6 +14257,7 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"optional": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
@ -14174,6 +14276,7 @@
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
@ -14206,6 +14309,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"optional": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
@ -14218,6 +14322,7 @@
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"optional": true,
"requires": {
"is-extendable": "^0.1.0"
}
@ -14229,7 +14334,11 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"optional": true
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"glob-parent": {
"version": "3.1.0",
@ -14269,6 +14378,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2"
},
@ -14278,6 +14388,7 @@
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
@ -14289,6 +14400,7 @@
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"optional": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
@ -14322,6 +14434,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"optional": true,
"requires": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
@ -14812,7 +14925,11 @@
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"optional": true
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"glob-parent": {
"version": "3.1.0",

View File

@ -21,9 +21,12 @@
"@angular/platform-browser": "~10.0.4",
"@angular/platform-browser-dynamic": "~10.0.4",
"@angular/router": "~10.0.4",
"@ngx-pwa/local-storage": "^10.0.1",
"@types/mathjs": "^6.0.5",
"angular2-uuid": "^1.1.1",
"component": "^1.1.0",
"mathjs": "^7.0.2",
"ngx-md": "^8.1.6",
"redux": "^4.0.5",
"reselect": "^4.0.0",
"rxjs": "~6.5.5",

View File

@ -27,12 +27,7 @@
position="end"
[opened]="false"
>
<mat-toolbar>Settings</mat-toolbar>
<mat-nav-list>
<a mat-list-item [routerLink]="['/']"
><mat-icon color="accenovert">page</mat-icon> Test</a
>
</mat-nav-list>
<app-settings></app-settings>
</mat-sidenav>
<mat-sidenav-content>
<router-outlet></router-outlet>

View File

@ -37,6 +37,8 @@ export class AppComponent implements AfterViewInit {
private breakpointObserver: BreakpointObserver
) {
this.appService.getSavedPages();
this.appService.initDisplaySettings();
this.fontSize$.subscribe((size) => {
document.documentElement.style.setProperty('--font-size', size);
});

View File

@ -7,11 +7,16 @@ import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule } from '@angular/common/http';
import { NgxMdModule } from 'ngx-md';
import { SearchPage } from './search/components/search-page/search.page';
import { PassageComponent } from './search/components/passage/passage.component';
import { StrongsComponent } from './search/components/strongs/strongs.component';
import { WordsComponent } from './search/components/words/words.component';
import { NoteComponent } from './search/components/note/note.component';
import { SettingsComponent } from './common/components/settings/settings.component';
import { NoteEditModalComponent } from './search/components/note-edit-modal/note-edit-modal.component';
import { VersePickerModalComponent } from './search/components/verse-picker/verse-picker-modal.component';
import { MatCheckboxModule } from '@angular/material/checkbox';
@ -58,7 +63,10 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
PassageComponent,
StrongsComponent,
WordsComponent,
NoteComponent,
NoteEditModalComponent,
VersePickerModalComponent,
SettingsComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
@ -69,6 +77,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
AppRoutingModule,
BrowserAnimationsModule,
NgxMdModule.forRoot(),
MatSidenavModule,
MatToolbarModule,
MatIconModule,

View File

@ -5,8 +5,8 @@ import {
ElementRef,
Component,
} from '@angular/core';
import { CardItem, OpenData } from '../models/app-state';
import { BibleReference } from './bible-reference';
import { CardItem, OpenData } from '../../models/app-state';
import { BibleReference } from '../bible-reference';
import { Observable } from 'rxjs';
@Component({

View File

@ -0,0 +1,60 @@
<mat-toolbar>Search Settings</mat-toolbar>
<mat-nav-list>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.showStrongsAsModal"
(change)="toggleStrongsAsModal($event)"
>Show Strongs as Modal</mat-slide-toggle
>
</mat-list-item>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.appendCardToBottom"
(change)="toggleAppendCardToBottom($event)"
>Append Results Below</mat-slide-toggle
>
</mat-list-item>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.insertCardNextToItem"
(change)="toggleInsertCardNextToItem($event)"
>Insert Result Next to Card</mat-slide-toggle
>
</mat-list-item>
</mat-nav-list>
<mat-toolbar>Display Settings</mat-toolbar>
<mat-nav-list>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.showParagraphs"
(change)="toggleParagraphs($event)"
>Show Paragraphs</mat-slide-toggle
>
</mat-list-item>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.showParagraphHeadings"
(change)="toggleParagraphHeadings($event)"
>Show Paragraph Headings</mat-slide-toggle
>
</mat-list-item>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.showVerseNumbers"
(change)="toggleVerseNumbers($event)"
>Show Verse Numbers</mat-slide-toggle
>
</mat-list-item>
<mat-list-item>
<mat-slide-toggle
[checked]="this.displaySettings.showVersesOnNewLine"
(change)="toggleVersesOnNewLine($event)"
>Show Verses on New Line</mat-slide-toggle
>
</mat-list-item>
</mat-nav-list>

View File

@ -0,0 +1,80 @@
import { Component } from '@angular/core';
import { AppService } from 'src/app/services/app.service';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { SubscriberComponent } from '../subscriber.component';
import { DisplaySettings } from 'src/app/models/app-state';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent extends SubscriberComponent {
displaySettings: DisplaySettings;
constructor(private appService: AppService) {
super();
this.addSubscription(
this.appService.state$.subscribe((state) => {
this.displaySettings = state.displaySettings;
})
);
}
//#region Search Settings
toggleStrongsAsModal(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
showStrongsAsModal: toggle.checked,
});
}
toggleAppendCardToBottom(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
appendCardToBottom: toggle.checked,
});
}
toggleInsertCardNextToItem(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
insertCardNextToItem: toggle.checked,
});
}
//#endregion
//#region Passage Settings
toggleParagraphHeadings(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
showParagraphHeadings: toggle.checked,
});
}
toggleParagraphs(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
showParagraphs: toggle.checked,
});
}
toggleVerseNumbers(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
showVerseNumbers: toggle.checked,
});
}
toggleVersesOnNewLine(toggle: MatSlideToggleChange) {
this.appService.updateDisplaySettings({
...this.displaySettings,
showVersesOnNewLine: toggle.checked,
});
}
//#endregion
}

View File

@ -1,5 +1,3 @@
import { MatCardActions } from '@angular/material/card';
export interface AppState {
readonly savedPages: readonly SavedPage[];
readonly mainPages: readonly Page[];
@ -15,20 +13,27 @@ export interface Error {
readonly msg: string;
}
export type Data = BiblePassageResult | StrongsResult | WordLookupResult;
export type Data =
| BiblePassageResult
| StrongsResult
| WordLookupResult
| NoteItem;
export interface CardIcons {
readonly words: string;
readonly passage: string;
readonly strongs: string;
readonly note: string;
}
export interface DisplaySettings {
readonly showStrongsAsModal: boolean;
readonly appendCardToBottom: boolean;
readonly insertCardNextToItem: boolean;
readonly fontSize: number;
readonly fontFamily: string;
readonly showVersesOnNewLine: boolean;
readonly showVerseNumbers: boolean;
readonly showParagraphs: boolean;
@ -180,3 +185,18 @@ export interface WordToStem {
}
//#endregion
//#region Notes
export type NoteItem = {
// The Note id
readonly id: string;
// A note title.
readonly title: string;
// An optional cross reference to a bible passage.
readonly xref: string | null;
// The content of the note styled as markdown.
readonly content: string;
};
//#endregion

View File

@ -0,0 +1,31 @@
<div mat-dialog-title>
<mat-toolbar>
<mat-icon>edit</mat-icon>
<div class="title">Edit: {{ data.title }}</div>
<span class="close-button">
<button
mat-icon-button
mat-dialog-close
aria-label="Exit the verse picker"
>
<mat-icon>cancel</mat-icon>
</button>
</span>
</mat-toolbar>
</div>
<mat-dialog-content>
<form [formGroup]="noteForm">
<mat-form-field class="note-title">
<mat-label>Title</mat-label>
<input formControlName="title" matInput />
</mat-form-field>
<mat-form-field class="note-content">
<mat-label>Content</mat-label>
<textarea formControlName="content" matInput></textarea>
</mat-form-field>
</form>
</mat-dialog-content>
<div mat-dialog-actions>
<button mat-button (click)="save()">Save</button>
<button mat-button (click)="cancel()">Cancel</button>
</div>

View File

@ -0,0 +1,24 @@
.close-button {
float: right;
mat-icon {
font-size: 2rem;
}
}
.title {
width: 100%;
padding-left: 1rem;
font-size: 1.5rem;
}
.mat-form-field {
display: block;
}
.note-content {
min-width: 75vw;
textarea {
min-height: 15rem;
}
}

View File

@ -0,0 +1,44 @@
import { Component, Inject } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CardItem, NoteItem } from '../../../models/app-state';
import { AppService } from '../../../services/app.service';
@Component({
selector: 'app-note-edit-modal',
templateUrl: 'note-edit-modal.component.html',
styleUrls: ['./note-edit-modal.component.scss'],
})
export class NoteEditModalComponent {
noteForm: FormGroup;
data: NoteItem;
constructor(
@Inject(MAT_DIALOG_DATA) public cardItem: CardItem,
public dialogRef: MatDialogRef<NoteEditModalComponent>,
private appService: AppService,
private fb: FormBuilder
) {
this.data = cardItem.data as NoteItem;
this.noteForm = this.fb.group(this.data);
}
cancel() {
this.dialogRef.close();
}
save() {
this.appService.editNote(
{
...this.cardItem,
data: {
...this.cardItem.data,
title: this.noteForm.get('title').value,
content: this.noteForm.get('content').value,
},
},
this.cardItem
);
this.dialogRef.close();
}
}

View File

@ -0,0 +1,51 @@
<div class="card-title note-title">
<mat-icon aria-hidden="false" aria-label="Note Icon">{{
icon$ | async
}}</mat-icon>
<span *ngIf="cardItem.data">{{ cardItem.data.title }}</span>
<button
mat-icon-button
class="card-close-button"
aria-label="Remove the note card from the list"
(click)="close($event)"
>
<mat-icon>cancel</mat-icon>
</button>
</div>
<div class="card-content" *ngIf="cardItem" #note>
<ngx-md class="markdown" *ngIf="cardItem.data">{{
cardItem.data.content
}}</ngx-md>
</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 aria-label="Edit the note." (click)="edit()">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button aria-label="Delete the note." (click)="delete()">
<mat-icon>delete</mat-icon>
</button>
<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 Note</span>
</button>
</mat-menu>
</span>
</div>

View File

@ -0,0 +1,11 @@
.note-title {
background-color: var(--note-color-primary);
}
.card-close-button {
color: var(--note-color-accent);
}
.card-actions {
color: var(--note-color-primary);
}

View File

@ -0,0 +1,40 @@
import { Component, ViewChild, ElementRef } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { NoteEditModalComponent } from '../note-edit-modal/note-edit-modal.component';
import { CardComponent } from '../../../common/components/card.component';
import { AppService } from '../../../services/app.service';
@Component({
selector: 'app-note',
templateUrl: './note.component.html',
styleUrls: ['./note.component.scss'],
})
export class NoteComponent extends CardComponent {
@ViewChild('note') noteElement: ElementRef;
constructor(
protected elementRef: ElementRef,
private appService: AppService,
public dialog: MatDialog
) {
super(elementRef);
this.icon$ = appService.select((state) => state.cardIcons.note);
}
copy() {
const html = this.noteElement.nativeElement.innerHTML;
const text = this.noteElement.nativeElement.innerText;
this.copyToClip(text, html);
}
edit() {
this.dialog.open(NoteEditModalComponent, {
data: this.cardItem,
});
}
delete() {
this.appService.deleteNote(this.cardItem);
}
}

View File

@ -15,6 +15,6 @@
}
.paragraph-heading {
font-family: var(--passage-heading-font-family);
font-family: var(--card-heading-font-family);
font-weight: 600;
}

View File

@ -1,7 +1,7 @@
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { BibleReference } from '../../../common/bible-reference';
import { AppService } from '../../../services/app.service';
import { CardComponent } from '../../../common/card.component';
import { CardComponent } from '../../../common/components/card.component';
import { Paragraph } from '../../../models/app-state';
@Component({
@ -28,7 +28,7 @@ export class PassageComponent extends CardComponent implements OnInit {
(state) => state.displaySettings.showVerseNumbers
);
@ViewChild('passage') passageElement;
@ViewChild('passage') passageElement: ElementRef;
constructor(
protected elementRef: ElementRef,

View File

@ -55,6 +55,12 @@
(onClose)="removeCard(item)"
(onItemClicked)="getItemsNextToCard($event)"
></app-words>
<app-note
*ngIf="isNote(item)"
[cardItem]="item"
(onClose)="removeCard(item)"
(onItemClicked)="getItemsNextToCard($event)"
></app-note>
</mat-card>
</ng-container>
<ng-template #nocards>

View File

@ -7,7 +7,7 @@ import { OpenData, CardItem } from 'src/app/models/app-state';
import { BibleReference } from 'src/app/common/bible-reference';
import { MatDialog } from '@angular/material/dialog';
import { VersePickerModalComponent } from '../verse-picker/verse-picker-modal.component';
import { SubscriberComponent } from '../../../common/subscriber.component';
import { SubscriberComponent } from '../../../common/components/subscriber.component';
import {
MatAutocompleteTrigger,

View File

@ -1,6 +1,6 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { AppService } from '../../../services/app.service';
import { CardComponent } from '../../../common/card.component';
import { CardComponent } from '../../../common/components/card.component';
@Component({
selector: 'app-strongs',
@ -9,7 +9,7 @@ import { CardComponent } from '../../../common/card.component';
preserveWhitespaces: true,
})
export class StrongsComponent extends CardComponent {
@ViewChild('strongs') strongsElement;
@ViewChild('strongs') strongsElement: ElementRef;
constructor(
protected elementRef: ElementRef,

View File

@ -1,6 +1,6 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { AppService } from '../../../services/app.service';
import { CardComponent } from '../../../common/card.component';
import { CardComponent } from '../../../common/components/card.component';
import { WordLookupResult } from 'src/app/models/app-state';
@Component({
@ -10,7 +10,7 @@ import { WordLookupResult } from 'src/app/models/app-state';
preserveWhitespaces: true,
})
export class WordsComponent extends CardComponent {
@ViewChild('words') wordsElement;
@ViewChild('words') wordsElement: ElementRef;
constructor(
protected elementRef: ElementRef,

View File

@ -19,14 +19,30 @@ import {
WordLookupResult,
WordToStem,
IndexResult,
NoteItem,
DisplaySettings,
} from '../models/app-state';
import { Section, BibleReference } from '../common/bible-reference';
import { PageTitles, PageIcons } from '../constants';
import { createStateService } from '../common/state-service';
import * as math from 'mathjs';
import { UUID } from 'angular2-uuid';
import { StorageMap } from '@ngx-pwa/local-storage';
const initialState: AppState = {
cards: [],
cards: [
{
qry: 'UUIDGOESHERE',
dict: 'n/a',
type: 'Note',
data: {
id: UUID.UUID(),
xref: null,
title: 'Title Here',
content: '# Content Here\nIn Markdown format.',
},
},
],
autocomplete: [],
savedPages: [],
mainPages: [
@ -51,6 +67,7 @@ const initialState: AppState = {
words: 'font_download',
passage: 'menu_book',
strongs: 'article',
note: 'text_snippet',
},
};
@ -92,6 +109,10 @@ type AppAction =
| {
type: 'UPDATE_AUTOCOMPLETE';
words: string[];
}
| {
type: 'UPDATE_DISPLAY_SETTINGS';
settings: DisplaySettings;
};
function reducer(state: AppState, action: AppAction): AppState {
@ -101,6 +122,12 @@ function reducer(state: AppState, action: AppAction): AppState {
}
switch (action.type) {
case 'UPDATE_DISPLAY_SETTINGS': {
return {
...state,
displaySettings: action.settings,
};
}
case 'UPDATE_AUTOCOMPLETE': {
return {
...state,
@ -195,7 +222,12 @@ export class AppService extends createStateService(reducer, initialState) {
private searchIndexArray: string[];
private autocomplete: string[];
constructor(private http: HttpClient) {
private readonly dataPath = 'assets/data';
constructor(
private http: HttpClient,
private localStorageService: StorageMap
) {
super();
this.searchIndexArray = this.buildIndexArray().sort();
@ -229,6 +261,131 @@ export class AppService extends createStateService(reducer, initialState) {
});
console.log(msg);
}
//#region Display Settings
updateDisplaySettings(settings: DisplaySettings) {
this.saveSettingsApi(settings).subscribe(
// success
() => {
this.dispatch({
type: 'UPDATE_DISPLAY_SETTINGS',
settings,
});
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the settings weren't saved. :(",
},
});
}
);
}
async initDisplaySettings() {
const hasDisplaySettings = await this.localStorageService
.has('displaySettings')
.toPromise();
if (hasDisplaySettings) {
const settings = await this.getSettingsApi();
this.dispatch({
type: 'UPDATE_DISPLAY_SETTINGS',
settings,
});
}
}
private saveSettingsApi(settings: DisplaySettings) {
return this.localStorageService.set('displaySettings', settings);
}
private async getSettingsApi() {
return (await this.localStorageService
.get('displaySettings')
.toPromise()) as DisplaySettings;
}
//#endregion
//#region Notes
async getNote(qry: string, nextToItem: CardItem = null) {
const note = (await this.localStorageService
.get('notes/' + qry)
.toPromise()) as NoteItem;
const card = {
qry,
dict: 'n/a',
type: 'Note',
data: note,
};
this.dispatch({
type: 'ADD_CARD',
card,
nextToItem,
});
}
async editNote(newCard: CardItem, oldCard: CardItem) {
this.saveNoteApi(newCard.data as NoteItem).subscribe(
// success
() => {
this.dispatch({
type: 'UPDATE_CARD',
newCard,
oldCard,
});
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the note wasn't saved. :(",
},
});
}
);
}
async deleteNote(noteCard: CardItem) {
this.deleteNoteApi(noteCard.data as NoteItem).subscribe(
// success
() => {
this.removeCard(noteCard);
},
// error
() => {
this.dispatch({
type: 'UPDATE_ERROR',
error: {
// tslint:disable-next-line: quotemark
msg: "Something went wrong and the note wasn't saved. :(",
},
});
}
);
}
private deleteNoteApi(note: NoteItem) {
return this.localStorageService.delete('notes/' + note.id);
}
private saveNoteApi(note: NoteItem) {
return this.localStorageService.set('notes/' + note.id, note);
}
//#endregion
//#region Strongs
async getStrongs(
@ -419,7 +576,7 @@ export class AppService extends createStateService(reducer, initialState) {
try {
const d = await this.http
.get<BiblePassage>(
`assets/data/bibles/kjv_strongs/${section.start.book.book_number}-${i}.json`
`${this.dataPath}/bibles/kjv_strongs/${section.start.book.book_number}-${i}.json`
)
.toPromise();
chapters.push(d);
@ -525,7 +682,7 @@ export class AppService extends createStateService(reducer, initialState) {
private async getParagraphMarkers(): Promise<HashTable<Paragraph>> {
const paras = await this.http
.get<HashTable<Paragraph>>('assets/data/bibles/paras.json')
.get<HashTable<Paragraph>>(`${this.dataPath}/bibles/paras.json`)
.toPromise();
this.dispatch({
type: 'UPDATE_PARAGRAPHS',
@ -631,7 +788,7 @@ export class AppService extends createStateService(reducer, initialState) {
if (stem <= this.searchIndexArray[0]) {
results.unshift(
await this.getSearchReferences(
'assets/data/index/' + this.searchIndexArray[0] + 'idx.json',
`${this.dataPath}/index/${this.searchIndexArray[0]}idx.json`,
stem
)
);
@ -647,7 +804,7 @@ export class AppService extends createStateService(reducer, initialState) {
) {
results.unshift(
await this.getSearchReferences(
'assets/data/index/' + this.searchIndexArray[w] + 'idx.json',
`${this.dataPath}/index/${this.searchIndexArray[w]}idx.json`,
stem
)
);
@ -692,7 +849,7 @@ export class AppService extends createStateService(reducer, initialState) {
this.wordToStem = new Map<string, string>();
try {
const r = await this.http
.get<WordToStem[]>('assets/data/index/word_to_stem_idx.json')
.get<WordToStem[]>(`${this.dataPath}/index/word_to_stem_idx.json`)
.toPromise();
// find the right word
@ -1039,7 +1196,7 @@ export class AppService extends createStateService(reducer, initialState) {
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')
.get<WordToStem[]>(`${this.dataPath}/index/word_to_stem_idx.json`)
.toPromise();
this.autocomplete = data.map((o) => o.w);
}

View File

@ -4,6 +4,7 @@ html {
--primary-color: #333;
--card-font: Roboto, Helvetica, Arial, sans-serif;
--card-heading-font-family: "Roboto Condensed";
--card-border-radius: 0.2em;
--card-title: #fff;
--card-color: #000;
@ -11,16 +12,16 @@ html {
--passage-color-primary: rgb(25, 68, 109);
--passage-color-accent: rgb(122, 166, 206);
--passage-heading-font-family: "Roboto Condensed";
--strongs-color-primary: rgb(17, 70, 29);
--strongs-color-accent: rgb(122, 206, 143);
--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";
--note-color-primary: rgb(71, 1, 54);
--note-color-accent: rgb(165, 86, 145);
}
body {
@ -106,15 +107,34 @@ a {
width: 100%;
}
mat-h2,
.mat-h1,
.mat-headline,
.mat-typography h1 {
font-family: var(--card-heading-font-family) !important;
font-size: calc(var(--font-size) * 2) !important;
line-height: calc(var(--font-size) * 2.5) !important;
}
.mat-h2,
.mat-title,
.mat-typography h2 {
font-family: var(--card-heading-font-family) !important;
font-size: calc(var(--font-size) * 1.5) !important;
line-height: calc(var(--font-size) * 1.7) !important;
}
.mat-h3,
.mat-subheading-2,
.mat-typography h3 {
font-family: var(--card-heading-font-family) !important;
font-size: calc(var(--font-size) * 1.1) !important;
line-height: calc(var(--font-size) * 1.2) !important;
}
.mat-h4,
.mat-subheading-1,
.mat-typography h4 {
font-family: var(--card-heading-font-family) !important;
font-size: var(--font-size) !important;
line-height: calc(var(--font-size) * 1.1) !important;
}