diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3ee0f61..16dd4f5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -13,6 +13,22 @@ import { ComponentsModule } from './components/components.module'; import { TreeModule } from 'angular-tree-component'; import {AgGridModule} from 'ag-grid-angular'; import {MonacoEditorModule} from 'ngx-monaco-editor'; +import { APP_BASE_HREF, PlatformLocation } from '@angular/common'; + +/** + * This function is used internal to get a string instance of the `` value from `index.html`. + * This is an exported function, instead of a private function or inline lambda, to prevent this error: + * + * `Error encountered resolving symbol values statically.` + * `Function calls are not supported.` + * `Consider replacing the function or lambda with a reference to an exported function.` + * + * @param platformLocation an Angular service used to interact with a browser's URL + * @return a string instance of the `` value from `index.html` + */ +export function getBaseHref(platformLocation: PlatformLocation): string { + return platformLocation.getBaseHrefFromDOM(); +} @NgModule({ declarations: [AppComponent], @@ -30,7 +46,12 @@ import {MonacoEditorModule} from 'ngx-monaco-editor'; providers: [ StatusBar, SplashScreen, - { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } + { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, + { + provide: APP_BASE_HREF, + useFactory: getBaseHref, + deps: [PlatformLocation] + } ], bootstrap: [AppComponent] }) diff --git a/src/app/components/editor/files/files.component.html b/src/app/components/editor/files/files.component.html index 23f0f0a..df587fd 100644 --- a/src/app/components/editor/files/files.component.html +++ b/src/app/components/editor/files/files.component.html @@ -2,6 +2,7 @@
+ Upload Drop Files
diff --git a/src/app/components/editor/files/files.component.scss b/src/app/components/editor/files/files.component.scss index 46ebbe3..2a2d50b 100644 --- a/src/app/components/editor/files/files.component.scss +++ b/src/app/components/editor/files/files.component.scss @@ -1,4 +1,5 @@ div.files-wrapper { border: 2px solid #8c8c8c; border-radius: 3px; + margin-top: 15px; } diff --git a/src/app/components/editor/files/files.component.ts b/src/app/components/editor/files/files.component.ts index d072f51..6173fe5 100644 --- a/src/app/components/editor/files/files.component.ts +++ b/src/app/components/editor/files/files.component.ts @@ -1,8 +1,9 @@ -import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {Component, ElementRef, EventEmitter, Inject, Input, OnInit, Output, ViewChild} from '@angular/core'; import HostRecord from '../../../structures/HostRecord'; import {ApiService} from '../../../service/api.service'; import {AlertController} from '@ionic/angular'; import {Observable} from 'rxjs'; +import { APP_BASE_HREF } from '@angular/common'; @Component({ selector: 'editor-files', @@ -23,6 +24,7 @@ export class FilesComponent implements OnInit { constructor( protected api: ApiService, protected alerts: AlertController, + @Inject(APP_BASE_HREF) private baseHref: string ) { } ngOnInit() { @@ -39,6 +41,10 @@ export class FilesComponent implements OnInit { this.uploadForm.nativeElement.submit(); } + getReturnUrl() { + return `${this.baseHref}editor;id=${this.hostRecord.PageId}`; + } + downloadFile(fileRecord) { // tslint:disable-next-line:max-line-length window.open(this.api._build_url(`files/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}/${fileRecord._id}`), '_blank'); diff --git a/src/app/components/editor/host/host.component.html b/src/app/components/editor/host/host.component.html index 071815b..fd27e72 100644 --- a/src/app/components/editor/host/host.component.html +++ b/src/app/components/editor/host/host.component.html @@ -15,14 +15,17 @@ class="host-host ion-padding" >
  • +
    +
    +
    this.listLines.push(group.value)); + setTimeout(() => { + values.forEach((group, i) => { + const el = this.liItems.toArray()[i].nativeElement; + el.className += ` node-indentation-level-num-${group.indentationLevel}`; + }); + }, 0); + } + } onRecordChange($event) { this.recordChange.emit($event); @@ -27,13 +38,13 @@ export class HostComponent implements OnInit { onKeyUp($event) { const innerText = this.hostContainer.nativeElement.innerText.trim() - if ( $event.code === 'Enter' + if ( $event.code === 'Enter' && this.record.isNorm() && !$event.shiftKey && ( this.record.type !== 'block_code' - || (innerText.endsWith('```') && (innerText.match(/`/g) || []).length >= 6) + || (innerText.endsWith('```') && (innerText.match(/`/g) || []).length >= 6) // TODO don't add new if cursor in block ) ) { - this.newHostRequested.emit(this); this.hostContainer.nativeElement.innerText = this.hostContainer.nativeElement.innerText.trim(); + this.newHostRequested.emit(this); } else if ( $event.code === 'Backspace' && !this.hostContainer.nativeElement.innerText.trim() ) { this.destroyHostRequested.emit(this); } @@ -50,60 +61,119 @@ export class HostComponent implements OnInit { this.record.type = 'block_code'; } else if ( innerText.startsWith('http') ) { this.record.type = 'click_link'; - } else if ( false && innerText.startsWith('-') || innerText.startsWith(' -') ) { - // this.record.type = 'ul'; + } else if ( innerText === '---' ) { + this.record.type = 'page_sep'; + } else if ( innerText.startsWith('-') || innerText.startsWith(' -') ) { + this.record.type = 'ul'; this.listLines = [this.record.value]; setTimeout(() => { - const item = this.liItems.toArray()[0].nativeElement; - const s = window.getSelection(); - const r = document.createRange(); - r.setStart(item, 0); - r.setEnd(item, 0); - s.removeAllRanges(); - s.addRange(r); + this.focusStart(this.liItems.toArray()[0].nativeElement); }, 0); } } - onRequestDelete($event) { - this.destroyHostRequested.emit(this); + onUlKeyDown($event, index) { + if ( $event.code === 'Tab' ) { + $event.preventDefault(); + const elem = this.liItems.toArray()[index]; + let currentLevel = 0; + + elem.nativeElement.className.split(' ').some(x => { + if ( x.startsWith('node-indentation-level-num-') ) { + currentLevel = Number(x.replace('node-indentation-level-num-', '')); + return true; + } + }); + + const newLevel = $event.shiftKey ? currentLevel - 1 : currentLevel + 1; + if ( newLevel <= 5 && newLevel >= 0 ) { + const existing = elem.nativeElement.className.split(' ').filter(x => !x.startsWith('node-indentation-level-num-')); + existing.push(`node-indentation-level-num-${newLevel}`); + + elem.nativeElement.className = existing.join(' '); + } + } } - onLIKeyUp($event, i) { - if ( $event.code === 'Enter' ) { - /*const newListLines = []; - this.liItems.forEach((li, index) => { - newListLines.push(li.nativeElement.innerText.trim()); - if ( index === i ) { - newListLines.push(''); + onUlKeyUp($event, i) { + if ( $event.code === 'Enter' && !$event.shiftKey ) { + const e = this.liItems.toArray()[i].nativeElement; + e.innerText = e.innerText.trim(); + if ( this.liItems.toArray()[i].nativeElement.innerText.trim() === '' ) { + this.newHostRequested.emit(this); + } else { + this.listLines.push(''); + setTimeout(() => { + this.focusStart(this.liItems.toArray()[i + 1].nativeElement); + + let newLevel = 0; + this.liItems.toArray()[i].nativeElement.className.split(' ').some(x => { + if ( x.startsWith('node-indentation-level-num-') ) { + newLevel = Number(x.replace('node-indentation-level-num-', '')); + return true; + } + }); + + const classes = this.liItems.toArray()[i + 1].nativeElement.className + .split(' ') + .filter(x => !x.startsWith('node-indentation-level-num-')); + classes.push(`node-indentation-level-num-${newLevel}`); + this.liItems.toArray()[i + 1].nativeElement.className = classes.join(' '); + }, 0); + } + } else if ( $event.code === 'Backspace' && this.liItems.toArray()[i].nativeElement.innerText.trim() === '' ) { + const newLines = []; + this.liItems.toArray().forEach((elem, index) => { + if ( index !== i ) { + newLines.push(elem.innerText ? elem.innerText.trim() : ''); } }); - this.listLines = newListLines;*/ - // this.listLines[i] = this.liItems[i].innerText.trim() - // const newLines = [] - // this.listLines.forEach((rec, x) => { - // newLines.push(rec.trim()); - // if ( i === x ) { - // newLines.push(''); - // } - // }) - - - // this.listLines = newLines; - - // setTimeout(() => { - // const item = this.liItems.toArray()[i + 1].nativeElement; - // const s = window.getSelection(); - // const r = document.createRange(); - // r.setStart(item, 0); - // r.setEnd(item, 0); - // s.removeAllRanges(); - // s.addRange(r); - // }, 10); + this.listLines = newLines; + + if ( i === 0 && this.listLines.length === 0 ) { + this.destroyHostRequested.emit(this); + } else { + setTimeout(() => { + this.focusEnd(this.liItems.toArray()[i - 1].nativeElement); + }, 0); + } + } else if ( $event.code === 'ArrowDown' ) { + const liArr = this.liItems.toArray(); + if ( liArr.length > i + 1 ) { + setTimeout(() => { + this.focusStart(this.liItems.toArray()[i + 1].nativeElement); + }, 0); + } + } else if ( $event.code === 'ArrowUp' ) { + if ( i !== 0 ) { + setTimeout(() => { + this.focusStart(this.liItems.toArray()[i - 1].nativeElement); + }, 0); + } + } else { + const recordValue = this.liItems.toArray().map(item => { + const elem = item.nativeElement; + const value = elem.innerText.trim(); + let indentationLevel = 0; + elem.className.split(' ').some(x => { + if ( x.startsWith('node-indentation-level-num-') ) { + indentationLevel = x.replace('node-indentation-level-num-', ''); + return true; + } + }); + + return {value, indentationLevel}; + }); + + this.record.value = JSON.stringify(recordValue); } } + onRequestDelete($event) { + this.destroyHostRequested.emit(this); + } + onRequestParentSave($event) { this.saveHostRequested.emit(this); } @@ -114,4 +184,48 @@ export class HostComponent implements OnInit { } } + takeFocus(fromTop = true) { + if ( this.record.type === 'ul' ) { + if ( fromTop ) { + this.focusStart(this.liItems.toArray()[0].nativeElement); + } else { + this.focusEnd(this.liItems.toArray().reverse()[0].nativeElement); + } + } else { + if ( fromTop ) { + this.focusStart(this.hostContainer.nativeElement); + } else { + this.focusEnd(this.hostContainer.nativeElement); + } + } + } + + // TODO return an observable here, probably + focusEnd(item) { + const s = window.getSelection(); + const r = document.createRange(); + r.setStart(item, 0); + r.setEnd(item, 0); + s.removeAllRanges(); + s.addRange(r); + } + + // TODO return an observable here, probably + focusStart(item) { + const s = window.getSelection(); + const r = document.createRange(); + r.setStart(item, 0); + r.setEnd(item, 0); + s.removeAllRanges(); + s.addRange(r); + + setTimeout(() => { + const r2 = document.createRange(); + r2.selectNodeContents(item); + r2.collapse(false); + const s2 = window.getSelection(); + s2.removeAllRanges(); + s2.addRange(r2); + }, 0); + } } diff --git a/src/app/components/editor/node-picker/node-picker.component.html b/src/app/components/editor/node-picker/node-picker.component.html index 56fde2c..b14eb8d 100644 --- a/src/app/components/editor/node-picker/node-picker.component.html +++ b/src/app/components/editor/node-picker/node-picker.component.html @@ -19,6 +19,10 @@ Heading 4 + + + Unordered List + Monospace Block @@ -39,4 +43,8 @@ Upload Files + + + Horizontal Row + diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html index 3e6416a..8ba7b40 100644 --- a/src/app/pages/editor/editor.page.html +++ b/src/app/pages/editor/editor.page.html @@ -16,7 +16,7 @@ -
    +
    -
    +
    Add Node Save
    diff --git a/src/app/pages/editor/editor.page.ts b/src/app/pages/editor/editor.page.ts index 822fc10..4ec60ee 100644 --- a/src/app/pages/editor/editor.page.ts +++ b/src/app/pages/editor/editor.page.ts @@ -62,16 +62,15 @@ export class EditorPage implements OnInit { const hostRec = new HostRecord(defValue); hostRec.type = arg.data; hostRec.PageId = this.pageRecord.UUID; + + if ( hostRec.type === 'ul' ) { + hostRec.value = JSON.stringify([{value: '', indentationLevel: 0}]); + } + this.hostRecords.push(hostRec); if ( hostRec.isNorm() ) { setTimeout(() => { - const host = this.editorHosts.toArray().reverse()[0].hostContainer.nativeElement; - const s = window.getSelection(); - const r = document.createRange(); - r.setStart(host, defValue.length); - r.setEnd(host, defValue.length); - s.removeAllRanges(); - s.addRange(r); + this.editorHosts.toArray().reverse()[0].takeFocus(); }, 0); } else { this.onSaveClick(); @@ -96,6 +95,8 @@ export class EditorPage implements OnInit { return '```'; } else if ( type === 'click_link' ) { return 'https://'; + } else if ( type === 'page_sep' ) { + return '---'; } else { return ''; } @@ -115,15 +116,8 @@ export class EditorPage implements OnInit { 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); + this.editorHosts.toArray()[insertAfter + 1].takeFocus(); }, 0); - } onDestroyHostRequested($event) { @@ -148,14 +142,10 @@ export class EditorPage implements OnInit { focusIndex = removedIndex - 1; } + console.log({removedIndex, focusIndex, edHArr: this.editorHosts.toArray()}); + 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); + this.editorHosts.toArray()[focusIndex].takeFocus(false); } }, 0); } @@ -167,7 +157,6 @@ export class EditorPage implements OnInit { index = i; } }); - return index; } diff --git a/src/app/structures/HostRecord.ts b/src/app/structures/HostRecord.ts index 36f9215..3fecb07 100644 --- a/src/app/structures/HostRecord.ts +++ b/src/app/structures/HostRecord.ts @@ -1,6 +1,9 @@ export default class HostRecord { public value = ''; - public type: 'paragraph'|'header1'|'header2'|'header3'|'header4'|'block_code'|'click_link'|'database_ref'|'ul'|'code_ref'|'file_ref' = 'paragraph'; + public type: 'paragraph' + |'header1'|'header2'|'header3'|'header4' + |'block_code'|'click_link'|'database_ref' + |'ul'|'code_ref'|'file_ref'|'page_sep' = 'paragraph'; public CreatedAt: string; public PageId: string; @@ -13,7 +16,7 @@ export default class HostRecord { } public isNorm() { - return ['paragraph', 'header1', 'header2', 'header3', 'header4', 'block_code', 'click_link'].includes(this.type); + return ['paragraph', 'header1', 'header2', 'header3', 'header4', 'block_code', 'click_link', 'page_sep'].includes(this.type); } load(data: any) {