From b0bc6ca20c8d6368d79a5897ad2fad2d6e1f74d9 Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sat, 8 Feb 2020 03:36:14 -0600 Subject: [PATCH] Initial editor functionality --- src/app/app-routing.module.ts | 4 + src/app/app.component.ts | 5 ++ src/app/app.module.ts | 2 + src/app/components/components.module.ts | 15 ++++ .../editor/host/host.component.html | 13 +++ .../editor/host/host.component.scss | 26 ++++++ .../editor/host/host.component.spec.ts | 24 +++++ .../components/editor/host/host.component.ts | 48 ++++++++++ .../editor/paragraph/paragraph.component.html | 3 + .../editor/paragraph/paragraph.component.scss | 0 .../paragraph/paragraph.component.spec.ts | 24 +++++ .../editor/paragraph/paragraph.component.ts | 14 +++ src/app/pages/editor/editor-routing.module.ts | 17 ++++ src/app/pages/editor/editor.module.ts | 22 +++++ src/app/pages/editor/editor.page.html | 26 ++++++ src/app/pages/editor/editor.page.scss | 0 src/app/pages/editor/editor.page.spec.ts | 24 +++++ src/app/pages/editor/editor.page.ts | 90 +++++++++++++++++++ src/app/structures/HostRecord.ts | 9 ++ 19 files changed, 366 insertions(+) create mode 100644 src/app/components/components.module.ts create mode 100644 src/app/components/editor/host/host.component.html create mode 100644 src/app/components/editor/host/host.component.scss create mode 100644 src/app/components/editor/host/host.component.spec.ts create mode 100644 src/app/components/editor/host/host.component.ts create mode 100644 src/app/components/editor/paragraph/paragraph.component.html create mode 100644 src/app/components/editor/paragraph/paragraph.component.scss create mode 100644 src/app/components/editor/paragraph/paragraph.component.spec.ts create mode 100644 src/app/components/editor/paragraph/paragraph.component.ts create mode 100644 src/app/pages/editor/editor-routing.module.ts create mode 100644 src/app/pages/editor/editor.module.ts create mode 100644 src/app/pages/editor/editor.page.html create mode 100644 src/app/pages/editor/editor.page.scss create mode 100644 src/app/pages/editor/editor.page.spec.ts create mode 100644 src/app/pages/editor/editor.page.ts create mode 100644 src/app/structures/HostRecord.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b464e7d..70e847d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -14,6 +14,10 @@ const routes: Routes = [ { path: 'list', loadChildren: () => import('./list/list.module').then(m => m.ListPageModule) + }, + { + path: 'editor', + loadChildren: () => import('./pages/editor/editor.module').then( m => m.EditorPageModule) } ]; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c013bef..b5cd3cc 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -20,6 +20,11 @@ export class AppComponent { title: 'List', url: '/list', icon: 'list' + }, + { + title: 'Editor', + url: '/editor', + icon: 'edit' } ]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0726cf5..5b66ac9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,6 +9,7 @@ import { StatusBar } from '@ionic-native/status-bar/ngx'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import {HttpClientModule} from '@angular/common/http'; +import {ComponentsModule} from './components/components.module'; @NgModule({ declarations: [AppComponent], @@ -18,6 +19,7 @@ import {HttpClientModule} from '@angular/common/http'; IonicModule.forRoot(), AppRoutingModule, HttpClientModule, + ComponentsModule, ], providers: [ StatusBar, diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts new file mode 100644 index 0000000..c2e123b --- /dev/null +++ b/src/app/components/components.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {HostComponent} from './editor/host/host.component'; + +@NgModule({ + declarations: [HostComponent], + imports: [ + CommonModule + ], + entryComponents: [HostComponent], + exports: [ + HostComponent + ] +}) +export class ComponentsModule { } diff --git a/src/app/components/editor/host/host.component.html b/src/app/components/editor/host/host.component.html new file mode 100644 index 0000000..c42ebd0 --- /dev/null +++ b/src/app/components/editor/host/host.component.html @@ -0,0 +1,13 @@ + +
+ {{ record.value }} +
+
diff --git a/src/app/components/editor/host/host.component.scss b/src/app/components/editor/host/host.component.scss new file mode 100644 index 0000000..2d6a910 --- /dev/null +++ b/src/app/components/editor/host/host.component.scss @@ -0,0 +1,26 @@ +.host-host.header1 { + font-weight: bold; + font-size: 24pt; +} + +.host-host.header2 { + font-weight: bold; + font-size: 21pt; +} + +.host-host.header3 { + font-weight: bold; + font-size: 18pt; +} + +.host-host.header4 { + font-weight: bold; + font-size: 16pt; +} + +.host-host.block_code { + font-family: monospace; + font-size: 12pt; + background-color: #ddd; + margin-bottom: 15px; +} diff --git a/src/app/components/editor/host/host.component.spec.ts b/src/app/components/editor/host/host.component.spec.ts new file mode 100644 index 0000000..5eb2e63 --- /dev/null +++ b/src/app/components/editor/host/host.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { HostComponent } from './host.component'; + +describe('HostComponent', () => { + let component: HostComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ HostComponent ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(HostComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/editor/host/host.component.ts b/src/app/components/editor/host/host.component.ts new file mode 100644 index 0000000..3cdcca5 --- /dev/null +++ b/src/app/components/editor/host/host.component.ts @@ -0,0 +1,48 @@ +import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import HostRecord from '../../../structures/HostRecord'; + +@Component({ + selector: 'editor-host', + templateUrl: './host.component.html', + styleUrls: ['./host.component.scss'], +}) +export class HostComponent implements OnInit { + @Input() record: HostRecord; + @Output() recordChange = new EventEmitter(); + @Output() newHostRequested = new EventEmitter(); + @Output() destroyHostRequested = new EventEmitter(); + @ViewChild('hostContainer', {static: false}) hostContainer: ElementRef; + + constructor() { } + + ngOnInit() {} + + onKeyUp($event) { + const innerText = this.hostContainer.nativeElement.innerText.trim() + if ( $event.code === 'Enter' + && ( this.record.type !== 'block_code' + || (innerText.endsWith('```') && (innerText.match(/`/g) || []).length >= 6) + ) + ) { + this.newHostRequested.emit(this); + this.hostContainer.nativeElement.innerText = this.hostContainer.nativeElement.innerText.trim(); + } else if ( $event.code === 'Backspace' && !this.hostContainer.nativeElement.innerText.trim() ) { + this.destroyHostRequested.emit(this); + } + + if ( innerText.startsWith('# ') ) { + this.record.type = 'header1'; + } else if ( innerText.startsWith('## ') ) { + this.record.type = 'header2'; + } else if ( innerText.startsWith('### ') ) { + this.record.type = 'header3'; + } else if ( innerText.startsWith('#### ') ) { + this.record.type = 'header4'; + } else if ( innerText.startsWith('```') ) { + this.record.type = 'block_code'; + } else { + this.record.type = 'paragraph'; + } + } + +} diff --git a/src/app/components/editor/paragraph/paragraph.component.html b/src/app/components/editor/paragraph/paragraph.component.html new file mode 100644 index 0000000..b1c4c67 --- /dev/null +++ b/src/app/components/editor/paragraph/paragraph.component.html @@ -0,0 +1,3 @@ +

+ paragraph works! +

diff --git a/src/app/components/editor/paragraph/paragraph.component.scss b/src/app/components/editor/paragraph/paragraph.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/editor/paragraph/paragraph.component.spec.ts b/src/app/components/editor/paragraph/paragraph.component.spec.ts new file mode 100644 index 0000000..c0ffa47 --- /dev/null +++ b/src/app/components/editor/paragraph/paragraph.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { ParagraphComponent } from './paragraph.component'; + +describe('ParagraphComponent', () => { + let component: ParagraphComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ParagraphComponent ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(ParagraphComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/editor/paragraph/paragraph.component.ts b/src/app/components/editor/paragraph/paragraph.component.ts new file mode 100644 index 0000000..7cc1ff6 --- /dev/null +++ b/src/app/components/editor/paragraph/paragraph.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-paragraph', + templateUrl: './paragraph.component.html', + styleUrls: ['./paragraph.component.scss'], +}) +export class ParagraphComponent implements OnInit { + + constructor() { } + + ngOnInit() {} + +} diff --git a/src/app/pages/editor/editor-routing.module.ts b/src/app/pages/editor/editor-routing.module.ts new file mode 100644 index 0000000..46c1429 --- /dev/null +++ b/src/app/pages/editor/editor-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { EditorPage } from './editor.page'; + +const routes: Routes = [ + { + path: '', + component: EditorPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class EditorPageRoutingModule {} diff --git a/src/app/pages/editor/editor.module.ts b/src/app/pages/editor/editor.module.ts new file mode 100644 index 0000000..eb4263e --- /dev/null +++ b/src/app/pages/editor/editor.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { IonicModule } from '@ionic/angular'; + +import { EditorPageRoutingModule } from './editor-routing.module'; + +import { EditorPage } from './editor.page'; +import {ComponentsModule} from '../../components/components.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + EditorPageRoutingModule, + ComponentsModule + ], + declarations: [EditorPage] +}) +export class EditorPageModule {} diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html new file mode 100644 index 0000000..4fe70b8 --- /dev/null +++ b/src/app/pages/editor/editor.page.html @@ -0,0 +1,26 @@ + + + Note Editor + + + + + +
+ Save +
+
+ Hello, world! + +
+ +
+
+
+
diff --git a/src/app/pages/editor/editor.page.scss b/src/app/pages/editor/editor.page.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/editor/editor.page.spec.ts b/src/app/pages/editor/editor.page.spec.ts new file mode 100644 index 0000000..d854864 --- /dev/null +++ b/src/app/pages/editor/editor.page.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { EditorPage } from './editor.page'; + +describe('EditorPage', () => { + let component: EditorPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditorPage ], + imports: [IonicModule.forRoot()] + }).compileComponents(); + + fixture = TestBed.createComponent(EditorPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/editor/editor.page.ts b/src/app/pages/editor/editor.page.ts new file mode 100644 index 0000000..5d6f13e --- /dev/null +++ b/src/app/pages/editor/editor.page.ts @@ -0,0 +1,90 @@ +import {Component, OnInit, ViewChildren} from '@angular/core'; +import HostRecord from '../../structures/HostRecord'; + +@Component({ + selector: 'app-editor', + templateUrl: './editor.page.html', + styleUrls: ['./editor.page.scss'], +}) +export class EditorPage implements OnInit { + public hostRecords: Array = [new HostRecord('I am a record.')]; + + @ViewChildren('editorHosts') editorHosts; + + constructor() { } + + ngOnInit() { + console.log('Editor component: ', this); + } + + onNewHostRequested($event) { + const insertAfter = this.getIndexFromRecord($event.record); + const record = new HostRecord(''); + const newHosts = [] + this.hostRecords.forEach((rec, i) => { + newHosts.push(rec); + if ( i === insertAfter ) { + newHosts.push(record); + } + }) + + this.hostRecords = newHosts; + + setTimeout(() => { + const host = this.editorHosts.toArray()[insertAfter + 1].hostContainer.nativeElement; + const s = window.getSelection(); + const r = document.createRange(); + r.setStart(host, 0); + r.setEnd(host, 0); + s.removeAllRanges(); + s.addRange(r); + }, 0); + + } + + onDestroyHostRequested($event) { + let removedIndex = 0; + const newHostRecords = this.editorHosts.filter((host, i) => { + if ( $event.record === host.record ) { + removedIndex = i; + } + return host.record !== $event.record; + }); + + const removedHost = this.editorHosts[removedIndex]; + + const hostRecords = newHostRecords.map(host => host.record); + this.hostRecords = hostRecords; + + setTimeout(() => { + let focusIndex; + if ( removedIndex === 0 && this.editorHosts.toArray().length ) { + focusIndex = 0; + } else if ( removedIndex !== 0 ) { + focusIndex = removedIndex - 1; + } + + if ( focusIndex >= 0 ) { + const host = this.editorHosts.toArray()[focusIndex].hostContainer.nativeElement; + const s = window.getSelection(); + const r = document.createRange(); + r.setStart(host, 0); + r.setEnd(host, 0); + s.removeAllRanges(); + s.addRange(r); + } + }, 0); + } + + protected getIndexFromRecord(record) { + let index; + this.editorHosts.toArray().forEach((host, i) => { + if ( host.record === record ) { + index = i; + } + }); + + return index; + } + +} diff --git a/src/app/structures/HostRecord.ts b/src/app/structures/HostRecord.ts new file mode 100644 index 0000000..82d591a --- /dev/null +++ b/src/app/structures/HostRecord.ts @@ -0,0 +1,9 @@ +export default class HostRecord { + public value = ''; + + public type: 'paragraph'|'header1'|'header2'|'header3'|'header4'|'block_code' = 'paragraph'; + + constructor(value = '') { + this.value = value; + } +}