FEATURE: Create, Edit and Store notes

This commit is contained in:
Jeremy Wall 2019-11-29 20:09:42 -06:00
parent 2bf90d3a7e
commit 2f9c9900f4
15 changed files with 395 additions and 22 deletions

File diff suppressed because one or more lines are too long

View File

@ -922,14 +922,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -950,8 +948,7 @@
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
@ -1102,7 +1099,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -1110,14 +1106,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -1136,7 +1130,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -1230,7 +1223,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2165,6 +2157,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="
},
"angularfire2": {
"version": "5.0.0-rc.4",
"resolved": "https://registry.npmjs.org/angularfire2/-/angularfire2-5.0.0-rc.4.tgz",
@ -3266,6 +3263,17 @@
"source-map": "0.5.x"
}
},
"clipboard": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz",
"integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
@ -4801,6 +4809,12 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -6087,7 +6101,8 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
@ -6096,7 +6111,8 @@
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -6199,7 +6215,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -6209,6 +6226,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -6228,11 +6246,13 @@
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -6249,6 +6269,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -6327,7 +6348,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -6337,6 +6359,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6442,6 +6465,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6688,6 +6712,15 @@
"minimatch": "~3.0.2"
}
},
"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"
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@ -8865,6 +8898,11 @@
"object-visit": "^1.0.0"
}
},
"marked": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg=="
},
"math-expression-evaluator": {
"version": "1.2.17",
"resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
@ -9253,6 +9291,15 @@
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
"dev": true
},
"ngx-md": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/ngx-md/-/ngx-md-3.1.1.tgz",
"integrity": "sha512-3pvEprJLkBt3jlQWT4YyhzKOSXDvLk16tSLJDKs5KpOkLPA7D7ASNFnDvm0bRLgEnnJcrB8db4A3EdbAHcvpjA==",
"requires": {
"marked": "^0.3.9",
"prismjs": "^1.9.0"
}
},
"no-case": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
@ -11079,6 +11126,14 @@
"utila": "~0.4"
}
},
"prismjs": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz",
"integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==",
"requires": {
"clipboard": "^2.0.0"
}
},
"private": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@ -11967,6 +12022,12 @@
}
}
},
"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",
@ -13136,6 +13197,12 @@
"setimmediate": "^1.0.4"
}
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"tiny-lr": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz",

View File

@ -33,6 +33,7 @@
"@ionic-native/splash-screen": "4.3.1",
"@ionic-native/status-bar": "4.3.1",
"@ionic/storage": "2.1.3",
"angular2-uuid": "1.1.1",
"angularfire2": "^5.0.0-rc.4",
"cordova-android": "7.0.0",
"cordova-android-support-gradle-release": "^1.4.7",
@ -52,6 +53,7 @@
"ionic-angular": "3.9.0",
"ionic-plugin-keyboard": "^2.2.1",
"ionic2-auto-complete": "^1.6.2-alpha",
"ngx-md": "3.1.1",
"ionicons": "3.0.0",
"node-gyp": "6.0.1",
"node-sass": "4.13.0",

View File

@ -15,6 +15,8 @@ import { MyApp } from './app.component';
import { SearchPage } from '../pages/search/search';
import { Passage } from '../components/passage/passage';
import { Note } from '../components/note/note';
import { NoteCreateModal } from '../components/note-create-modal/note-create-modal';
import { Strongs } from '../components/strongs/strongs';
import { Words } from '../components/words/words';
import { Error } from '../components/error/error';
@ -31,6 +33,8 @@ import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AutoCompleteModule } from 'ionic2-auto-complete';
import { PagesService } from '../services/pages-service';
import { NotesService } from '../services/notes-service';
import { MarkdownModule } from 'ngx-md';
export const firebaseConfig = {
apiKey: 'AIzaSyA3UV4s56CV2EumgvZmyJBTyU-vhv0xhc8',
@ -50,6 +54,8 @@ export const firebaseConfig = {
SettingsModal,
Settings,
Passage,
Note,
NoteCreateModal,
Strongs,
StrongsModal,
VersePickerModal,
@ -60,6 +66,7 @@ export const firebaseConfig = {
imports: [
IonicModule.forRoot(MyApp),
IonicStorageModule.forRoot(),
MarkdownModule.forRoot(),
BrowserModule,
HttpClientModule,
HttpModule,
@ -81,8 +88,9 @@ export const firebaseConfig = {
VersePickerModal,
Words,
Error,
NoteCreateModal,
ErrorMessage
],
providers: [{ provide: ErrorHandler, useClass: IonicErrorHandler }, StatusBar, SplashScreen, ProfileService, PagesService]
providers: [{ provide: ErrorHandler, useClass: IonicErrorHandler }, StatusBar, SplashScreen, ProfileService, PagesService, NotesService]
})
export class AppModule { }

View File

@ -0,0 +1,24 @@
<ion-header>
<ion-toolbar>
<ion-title>
<ion-icon name="text" item-left></ion-icon> Create a Note
</ion-title>
<ion-buttons start>
<button ion-button (click)="dismiss()" large>
<ion-icon name="md-close"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content padding *ngIf="item !== undefined">
<ion-item>
<ion-label>Title</ion-label>
<ion-input text [(ngModel)]="item.title"></ion-input>
</ion-item>
<ion-item>
<ion-label>Text</ion-label>
<br>
<ion-textarea [(ngModel)]="item.content"></ion-textarea>
</ion-item>
<button ion-button (click)="save()">Save</button>
</ion-content>

View File

@ -0,0 +1,6 @@
note-create-modal {
font-family: var(--card-font);
textarea {
height: 100px;
}
}

View File

@ -0,0 +1,55 @@
import { EventEmitter, Component, Output, OnInit } from '@angular/core';
import { Platform, NavParams, ViewController } from 'ionic-angular';
import { NoteItem, NotesService } from '../../services/notes-service';
import { UUID } from 'angular2-uuid';
@Component({
selector: 'note-create-modal',
templateUrl: 'note-create-modal.html'
})
export class NoteCreateModal implements OnInit
{
@Output()
onItemSaved = new EventEmitter<NoteItem>();
item: NoteItem;
constructor(
public platform: Platform,
public params: NavParams,
public viewCtrl: ViewController,
private noteService: NotesService
)
{
this.onItemSaved.subscribe(item =>
{
this.noteService.saveNote(item)
.catch(err => console.log(err));
});
}
ngOnInit(): void
{
let data = this.params.get('data');
if (data !== undefined)
{
this.item = data as NoteItem;
}
else
{
this.item = { id: UUID.UUID(), title: "Test", xref: "", content: "this is the text" };
}
}
dismiss()
{
this.viewCtrl.dismiss();
}
save()
{
this.onItemSaved.emit(this.item);
this.dismiss();
}
}

View File

@ -0,0 +1,17 @@
<ion-item class="title note-title" (swipe)="close()">
<ion-icon name="book" item-left></ion-icon><span>Note: </span>
<span *ngIf="data !== undefined">{{ data.title }}</span>
<!-- TODO(jwall): Put the crossreference somewhere if it exists. -->
<button ion-button icon-only item-end large clear (click)="close()">
<ion-icon name="close-circle"></ion-icon>
</button>
</ion-item>
<ion-card-content>
<markdown *ngIf = "data !== undefined" [data]="data.content"></markdown>
</ion-card-content>
<div style="float: left">
<button ion-button icon-start clear (click)="edit()">
<ion-icon name="create"></ion-icon>
<div>Edit</div>
</button>
</div>

View File

@ -0,0 +1,16 @@
note {
a {
user-select: text !important;
cursor: pointer;
}
font-family: var(--card-font);
}
note .button {
color: #735992;
}
.note-title {
background-color: #e0bcff;
}

View File

@ -0,0 +1,76 @@
import { Component, EventEmitter, Output, Input, OnInit, ElementRef } from '@angular/core';
import { UUID } from 'angular2-uuid';
import { ModalController } from 'ionic-angular';
import { OpenData, CardItem, SearchPage } from '../../pages/search/search';
import { NoteItem, NotesService } from '../../services/notes-service';
import { NoteCreateModal } from '../../components/note-create-modal/note-create-modal';
@Component({
selector: 'note',
templateUrl: 'note.html'
})
export class Note implements OnInit
{
@Output()
onItemClicked = new EventEmitter<OpenData>();
@Output()
onClose = new EventEmitter<CardItem>();
@Input()
cardItem: CardItem;
@Input()
parent: SearchPage;
data: NoteItem | null;
constructor(private elementRef: ElementRef, private noteService: NotesService
, public modalCtrl: ModalController)
{
this.data = {id: UUID.UUID(), title: "A note!", xref: null, content: "Testing 1 2 3"};
}
ngOnInit(): void
{
this.noteService.getNoteAsPromise(this.cardItem.qry).then(note =>
{
console.log("Got note from service: ", note)
this.data = note;
}).catch(e => console.log(e));
}
close()
{
let d = 250;
this.elementRef.nativeElement.parentElement.animate({
transform: ['none', 'translate3d(110%, 0, 0)']
}, {
fill: 'forwards',
duration: d,
iterations: 1,
easing: 'ease-in-out'
});
setTimeout(() =>
{
this.onClose.emit(this.cardItem);
}, d);
}
edit()
{
const modal = this.modalCtrl.create(NoteCreateModal, { data: this.data });
modal.present();
}
delete()
{
this.noteService.deleteNote(this.data)
.catch(err => console.log(err));
this.close();
}
}

View File

@ -1,3 +1,11 @@
<ion-list>
<ion-list-header>
<ion-icon name="notes" item-left></ion-icon>Note Tools
</ion-list-header>
<ion-item>
<button ion-button block (click)='newNote()'>New Note</button>
</ion-item>
</ion-list>
<ion-list>
<ion-list-header>
<ion-icon name='search' item-left></ion-icon>Search Settings

View File

@ -1,5 +1,7 @@
import { Component } from '@angular/core';
import { ProfileService } from '../../services/profile-service';
import { NotesService } from '../../services/notes-service';
import { SearchPage } from '../../pages/search/search';
@Component({
selector: 'settings',
@ -7,7 +9,9 @@ import { ProfileService } from '../../services/profile-service';
})
export class Settings {
constructor(
public profileService: ProfileService
public profileService: ProfileService,
public noteService: NotesService,
public searchPage: SearchPage,
) {}
@ -24,7 +28,14 @@ export class Settings {
}
reset()
{
this.profileService.reset()
this.profileService.reset();
}
newNote() {
this.noteService.newNote().then(note => {
this.searchPage.updateUIwithItems("note:" + note.id, false)
this.profileService.localSave();
}).catch(e => console.log(e));
}
}

View File

@ -52,5 +52,6 @@
<words *ngIf='isWords(item.type)' [cardItem]='item' (onClose)='this.profileService.removeItem($event)'
(onItemClicked)='getItemsNextToCard($event)'></words>
<error *ngIf='isError(item.type)' [cardItem]='item' (onClose)='this.profileService.removeItem($event)'></error>
<note *ngIf="isNote(item.type)" [cardItem]="item" [parent]="self" (onClose)="removeItem($event)"></note>
</ion-card>
</ion-content>

View File

@ -230,6 +230,9 @@ export class SearchPage implements OnInit {
isPassage(t: string) {
return t === "Passage";
}
isNote(t: string) {
return t === "Note";
}
isStrongs(t: string) {
return t === "Strongs";
}
@ -259,8 +262,18 @@ export class SearchPage implements OnInit {
if (qs.hasOwnProperty(x)) {
let q = qs[x].trim();
if (q !== "") {
// its a search term.
if (q.search(/[0-9]/i) === -1) list.push({ qry: q, dict: "na", type: "Words" });
if (q.startsWith("note:")) {
// It's a note lookup
list.push({
qry: q.replace("note:", ""),
dict: "",
type: "Note"
});
}
else if (q.search(/[0-9]/i) === -1) {
// its a search term.
list.push({ qry: q, dict: "na", type: "Words" });
}
else if (q.search(/(H|G)[0-9]/i) !== -1) {
// its a strongs lookup
let dict = q.substring(0, 1);

View File

@ -0,0 +1,69 @@
import { Injectable, Component } from '@angular/core';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';
import { Storage } from '@ionic/storage';
import { UUID } from 'angular2-uuid';
import { setTimeout } from 'timers';
import { Note } from 'ionic-angular';
import { Output, EventEmitter } from '@angular/core';
@Injectable()
export class NotesService {
remoteLoggedIn: boolean;
constructor(private local: Storage) {
}
getNoteAsPromise(qry: string): Promise<NoteItem> {
return this.local.get("notes/" + qry);
}
deleteNote(note: NoteItem): Promise<any> {
return this.local.remove("notes/" + note.id);
}
saveNote(note: NoteItem): Promise<any> {
return this.local.set("notes/" + note.id, note);
}
newNote(): Promise<NoteItem> {
let note = {
id: UUID.UUID(),
xref: null,
title: "Title Here",
content: "# Content Here\nIn Markdown format.",
}
return this.saveNote(note)
}
newNoteForRef(xref: string): Promise<NoteItem> {
let note = {
id: UUID.UUID(),
xref: xref,
title: "Notes on " + xref,
content: "# Content Here\nIn Markdown format.",
}
return this.saveNote(note)
}
}
export type NoteItem = {
// The Note id
id: string,
// A note title.
title: string;
// An optional cross reference to a bible passage.
xref: string | null;
// The content of the note styled as markdown.
content: string;
};
type fbObject<T> = {
ref: AngularFireList<T>;
stream: Observable<T[]>;
};