This commit is contained in:
parent
8a53bc2888
commit
3861c1e72f
@ -1,13 +1,13 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<div
|
<div
|
||||||
*ngIf="record.type === 'paragraph' || record.type === 'header1' || record.type === 'header2' || record.type === 'header3' || record.type === 'header4' || record.type === 'block_code'"
|
*ngIf="record.type === 'paragraph' || record.type === 'header1' || record.type === 'header2' || record.type === 'header3' || record.type === 'header4' || record.type === 'block_code' || record.type === 'click_link'"
|
||||||
class="host-host ion-padding"
|
class="host-host ion-padding"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
(keyup)="onKeyUp($event)"
|
(keyup)="onKeyUp($event)"
|
||||||
(blur)="record.value=hostContainer.innerText"
|
(blur)="record.value=hostContainer.innerHTML"
|
||||||
|
(dblclick)="onHostDblClick()"
|
||||||
#hostContainer
|
#hostContainer
|
||||||
[ngClass]="{'paragraph': record.type === 'paragraph', 'header1': record.type === 'header1', 'header2': record.type === 'header2', 'header3': record.type === 'header3', 'header4': record.type === 'header4', 'block_code': record.type === 'block_code'}"
|
[ngClass]="{'paragraph': record.type === 'paragraph', 'header1': record.type === 'header1', 'header2': record.type === 'header2', 'header3': record.type === 'header3', 'header4': record.type === 'header4', 'block_code': record.type === 'block_code', 'click_link': record.type === 'click_link'}"
|
||||||
>
|
[innerHTML]="record.value.replace('\n', '<br>')"
|
||||||
{{ record.value }}
|
></div>
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -24,3 +24,12 @@
|
|||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.host-host.click_link {
|
||||||
|
color: #0141b0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -40,9 +40,17 @@ export class HostComponent implements OnInit {
|
|||||||
this.record.type = 'header4';
|
this.record.type = 'header4';
|
||||||
} else if ( innerText.startsWith('```') ) {
|
} else if ( innerText.startsWith('```') ) {
|
||||||
this.record.type = 'block_code';
|
this.record.type = 'block_code';
|
||||||
|
} else if ( innerText.startsWith('http') ) {
|
||||||
|
this.record.type = 'click_link';
|
||||||
} else {
|
} else {
|
||||||
this.record.type = 'paragraph';
|
this.record.type = 'paragraph';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onHostDblClick() {
|
||||||
|
if ( this.record.type === 'click_link' ) {
|
||||||
|
window.open(this.record.value.trim(), '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Note Editor</ion-title>
|
<ion-title contenteditable="true" #titleBar>{{ pageRecord.Name }}</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="editor-buttons">
|
|
||||||
<ion-button class="ion-padding ion-margin">Save</ion-button>
|
|
||||||
</div>
|
|
||||||
<div class="editor-root ion-padding">
|
<div class="editor-root ion-padding">
|
||||||
Hello, world!
|
|
||||||
|
|
||||||
<div class="host-container ion-padding">
|
<div class="host-container ion-padding">
|
||||||
<editor-host
|
<editor-host
|
||||||
#editorHosts
|
#editorHosts
|
||||||
@ -22,5 +17,9 @@
|
|||||||
></editor-host>
|
></editor-host>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="editor-buttons">
|
||||||
|
<ion-button (click)="onAddClick()" class="ion-padding ion-margin">Add Node</ion-button>
|
||||||
|
<ion-button (click)="onSaveClick()" class="ion-padding ion-margin">Save</ion-button>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {Component, OnInit, ViewChildren} from '@angular/core';
|
import {Component, Host, OnInit, ViewChild, ViewChildren} from '@angular/core';
|
||||||
import HostRecord from '../../structures/HostRecord';
|
import HostRecord from '../../structures/HostRecord';
|
||||||
|
import PageRecord from '../../structures/PageRecord';
|
||||||
|
import {PageService} from '../../service/page.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-editor',
|
selector: 'app-editor',
|
||||||
@ -7,16 +9,43 @@ import HostRecord from '../../structures/HostRecord';
|
|||||||
styleUrls: ['./editor.page.scss'],
|
styleUrls: ['./editor.page.scss'],
|
||||||
})
|
})
|
||||||
export class EditorPage implements OnInit {
|
export class EditorPage implements OnInit {
|
||||||
public hostRecords: Array<HostRecord> = [new HostRecord('I am a record.')];
|
public hostRecords: Array<HostRecord> = [new HostRecord('Click to edit page...')];
|
||||||
|
public pageRecord: PageRecord = new PageRecord();
|
||||||
|
|
||||||
@ViewChildren('editorHosts') editorHosts;
|
@ViewChildren('editorHosts') editorHosts;
|
||||||
|
@ViewChild('titleBar', {static: false}) titleBar;
|
||||||
|
|
||||||
constructor() { }
|
constructor(
|
||||||
|
protected pages: PageService,
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
console.log('Editor component: ', this);
|
console.log('Editor component: ', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ionViewDidEnter() {
|
||||||
|
const pageId = prompt('What is the ID of the page to load?');
|
||||||
|
this.pages.load(pageId).subscribe(pageRecord => {
|
||||||
|
this.pageRecord = pageRecord;
|
||||||
|
this.pages.get_nodes(pageRecord).subscribe((hosts: Array<HostRecord>) => {
|
||||||
|
this.hostRecords = hosts;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddClick() {
|
||||||
|
this.hostRecords.push(new HostRecord(''));
|
||||||
|
setTimeout(() => {
|
||||||
|
const host = this.editorHosts.toArray().reverse()[0].hostContainer.nativeElement;
|
||||||
|
const s = window.getSelection();
|
||||||
|
const r = document.createRange();
|
||||||
|
r.setStart(host, 0);
|
||||||
|
r.setEnd(host, 0);
|
||||||
|
s.removeAllRanges();
|
||||||
|
s.addRange(r);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
onNewHostRequested($event) {
|
onNewHostRequested($event) {
|
||||||
const insertAfter = this.getIndexFromRecord($event.record);
|
const insertAfter = this.getIndexFromRecord($event.record);
|
||||||
const record = new HostRecord('');
|
const record = new HostRecord('');
|
||||||
@ -87,4 +116,18 @@ export class EditorPage implements OnInit {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSaveClick() {
|
||||||
|
this.pageRecord.Name = this.titleBar.el.innerText.trim();
|
||||||
|
|
||||||
|
// First, save the page record itself
|
||||||
|
this.pages.save(this.pageRecord).subscribe(pageRecord => {
|
||||||
|
this.pageRecord = pageRecord;
|
||||||
|
|
||||||
|
// Now, save the nodes
|
||||||
|
this.pages.save_nodes(pageRecord, this.hostRecords).subscribe(result => {
|
||||||
|
this.hostRecords = result;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ApiService } from './api.service';
|
|
||||||
|
|
||||||
describe('ApiService', () => {
|
|
||||||
beforeEach(() => TestBed.configureTestingModule({}));
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
const service: ApiService = TestBed.get(ApiService);
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -24,11 +24,11 @@ export class ApiService {
|
|||||||
|
|
||||||
public request(endpoint, params = {}, method: 'get'|'post' = 'get'): Observable<ApiResponse> {
|
public request(endpoint, params = {}, method: 'get'|'post' = 'get'): Observable<ApiResponse> {
|
||||||
return new Observable<ApiResponse>(sub => {
|
return new Observable<ApiResponse>(sub => {
|
||||||
const data: any = {}
|
let data: any = {}
|
||||||
if ( method === 'get' ) {
|
if ( method === 'get' ) {
|
||||||
data.params = params;
|
data.params = params;
|
||||||
} else {
|
} else {
|
||||||
data.body = params;
|
data = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.http[method](this._build_url(endpoint), data).subscribe({
|
this.http[method](this._build_url(endpoint), data).subscribe({
|
||||||
|
74
src/app/service/page.service.ts
Normal file
74
src/app/service/page.service.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import {Host, Injectable} from '@angular/core';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import PageRecord from '../structures/PageRecord';
|
||||||
|
import {ApiService} from './api.service';
|
||||||
|
import HostRecord from '../structures/HostRecord';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class PageService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected api: ApiService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
load(id: string): Observable<PageRecord> {
|
||||||
|
return new Observable<PageRecord>(sub => {
|
||||||
|
this.api.get(`/page/${id}`).subscribe(response => {
|
||||||
|
if ( response.status === 200 ) {
|
||||||
|
sub.next(new PageRecord(response.data));
|
||||||
|
sub.complete();
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
save(page: PageRecord): Observable<PageRecord> {
|
||||||
|
return new Observable<PageRecord>(sub => {
|
||||||
|
this.api.post(`/page/${page.UUID}/save`, page.toSave()).subscribe(res => {
|
||||||
|
sub.next(new PageRecord(res.data));
|
||||||
|
sub.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get_nodes(page: PageRecord): Observable<Array<HostRecord>> {
|
||||||
|
return new Observable<Array<HostRecord>>(sub => {
|
||||||
|
this.api.get(`/page/${page.UUID}/nodes`).subscribe(res => {
|
||||||
|
const returns = [];
|
||||||
|
res.data.forEach(rec => {
|
||||||
|
const host = new HostRecord(rec.Value.Value);
|
||||||
|
host.load(rec);
|
||||||
|
returns.push(host);
|
||||||
|
});
|
||||||
|
|
||||||
|
sub.next(returns);
|
||||||
|
sub.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
save_nodes(page: PageRecord, nodes: Array<HostRecord>): Observable<Array<HostRecord>> {
|
||||||
|
return new Observable<Array<HostRecord>>(sub => {
|
||||||
|
nodes = nodes.map(x => {
|
||||||
|
x.PageId = page.UUID;
|
||||||
|
return x.toSave();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.api.post(`/page/${page.UUID}/nodes/save`, nodes).subscribe(res => {
|
||||||
|
const returns = [];
|
||||||
|
res.data.forEach(rec => {
|
||||||
|
const host = new HostRecord(rec.Value.Value);
|
||||||
|
host.load(rec);
|
||||||
|
returns.push(host);
|
||||||
|
});
|
||||||
|
|
||||||
|
sub.next(returns);
|
||||||
|
sub.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,54 @@
|
|||||||
export default class HostRecord {
|
export default class HostRecord {
|
||||||
public value = '';
|
public value = '';
|
||||||
|
public type: 'paragraph'|'header1'|'header2'|'header3'|'header4'|'block_code'|'click_link' = 'paragraph';
|
||||||
|
|
||||||
public type: 'paragraph'|'header1'|'header2'|'header3'|'header4'|'block_code' = 'paragraph';
|
public CreatedAt: string;
|
||||||
|
public PageId: string;
|
||||||
|
public UUID: string;
|
||||||
|
public UpdatedAt: string;
|
||||||
|
public Value: any;
|
||||||
|
|
||||||
constructor(value = '') {
|
constructor(value = '') {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load(data: any) {
|
||||||
|
this.type = data.Type;
|
||||||
|
|
||||||
|
[
|
||||||
|
'CreatedAt',
|
||||||
|
'PageId',
|
||||||
|
'UUID',
|
||||||
|
'UpdatedAt',
|
||||||
|
'Value',
|
||||||
|
].forEach(field => {
|
||||||
|
if ( field in data ) {
|
||||||
|
this[field] = data[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toSave() {
|
||||||
|
const data: any = {
|
||||||
|
Type: this.type
|
||||||
|
};
|
||||||
|
|
||||||
|
[
|
||||||
|
'CreatedAt',
|
||||||
|
'PageId',
|
||||||
|
'UUID',
|
||||||
|
'UpdatedAt',
|
||||||
|
'Value',
|
||||||
|
].forEach(field => {
|
||||||
|
if ( field in this ) {
|
||||||
|
data[field] = this[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( !data.Value ) {
|
||||||
|
data.Value = {};
|
||||||
|
}
|
||||||
|
data.Value.Value = this.value;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
58
src/app/structures/PageRecord.ts
Normal file
58
src/app/structures/PageRecord.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
export default class PageRecord {
|
||||||
|
public UUID: string;
|
||||||
|
public Name: string;
|
||||||
|
public OrgUserId: string;
|
||||||
|
public IsPublic = true;
|
||||||
|
public IsVisibleInMenu = true;
|
||||||
|
public ParentId: string;
|
||||||
|
public NodeIds: Array<string>;
|
||||||
|
public CreatedAt: Date;
|
||||||
|
public UpdatedAt: Date;
|
||||||
|
public CreatedUserId: string;
|
||||||
|
public UpdateUserId: string;
|
||||||
|
public ChildPageIds: Array<string>;
|
||||||
|
|
||||||
|
constructor(data: any = {Name: 'Click to edit title...'}) {
|
||||||
|
[
|
||||||
|
'UUID',
|
||||||
|
'Name',
|
||||||
|
'OrgUserId',
|
||||||
|
'IsPublic',
|
||||||
|
'IsVisibleInMenu',
|
||||||
|
'ParentId',
|
||||||
|
'NodeIds',
|
||||||
|
'CreatedAt',
|
||||||
|
'UpdatedAt',
|
||||||
|
'CreatedUserId',
|
||||||
|
'UpdateUserId',
|
||||||
|
'ChildPageIds'
|
||||||
|
].forEach(field => {
|
||||||
|
if ( field in data ) {
|
||||||
|
this[field] = data[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toSave() {
|
||||||
|
const data = {};
|
||||||
|
[
|
||||||
|
'UUID',
|
||||||
|
'Name',
|
||||||
|
'OrgUserId',
|
||||||
|
'IsPublic',
|
||||||
|
'IsVisibleInMenu',
|
||||||
|
'ParentId',
|
||||||
|
'NodeIds',
|
||||||
|
'CreatedAt',
|
||||||
|
'UpdatedAt',
|
||||||
|
'CreatedUserId',
|
||||||
|
'UpdateUserId',
|
||||||
|
'ChildPageIds'
|
||||||
|
].forEach(field => {
|
||||||
|
if ( field in this ) {
|
||||||
|
data[field] = this[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
backendBase: '/',
|
backendBase: '/api/v1',
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user