Add markdown editor node
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Garrett Mills 2020-10-16 12:20:16 -05:00
parent 74b7cdadc7
commit b588865137
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
15 changed files with 251 additions and 14 deletions

View File

@ -48,9 +48,15 @@
}, },
{ {
"input": "src/assets/font/fonts.css" "input": "src/assets/font/fonts.css"
},
{
"input": "node_modules/katex/dist/katex.min.css"
} }
], ],
"scripts": [] "scripts": [
"node_modules/marked/lib/marked.js",
"node_modules/katex/dist/katex.min.js"
]
}, },
"configurations": { "configurations": {
"production": { "production": {

92
package-lock.json generated
View File

@ -2126,6 +2126,11 @@
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true "dev": true
}, },
"@types/marked": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-1.1.0.tgz",
"integrity": "sha512-j8XXj6/l9kFvCwMyVqozznqpd/nk80krrW+QiIJN60Uu9gX5Pvn4/qPJ2YngQrR3QREPwmrE1f9/EWKVTFzoEw=="
},
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -3636,6 +3641,17 @@
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
"dev": true "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": { "cliui": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -3795,8 +3811,7 @@
"commander": { "commander": {
"version": "2.20.3", "version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
"dev": true
}, },
"commondir": { "commondir": {
"version": "1.0.1", "version": "1.0.1",
@ -4758,6 +4773,12 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true "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": { "depd": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -4992,6 +5013,11 @@
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true "dev": true
}, },
"emoji-toolkit": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-6.0.1.tgz",
"integrity": "sha512-QZZq0beHg753JxcBt89UBFqzwYNuMtXhNO+jY3viSAndewmn9biTE5glaro1RA0uWJ4hKqw4k1Mboe1M6sGkMA=="
},
"emojis-list": { "emojis-list": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
@ -6003,6 +6029,15 @@
"slash": "^3.0.0" "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"
}
},
"graceful-fs": { "graceful-fs": {
"version": "4.2.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@ -7830,6 +7865,14 @@
"source-map-support": "^0.5.5" "source-map-support": "^0.5.5"
} }
}, },
"katex": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.12.0.tgz",
"integrity": "sha512-y+8btoc/CK70XqcHqjxiGWBOeIL8upbS0peTPXTvgrh21n1RiWWcIpSWM+4uXq+IAgNh9YYQWdc7LVDPDAEEAg==",
"requires": {
"commander": "^2.19.0"
}
},
"killable": { "killable": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@ -8227,6 +8270,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"marked": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-1.2.0.tgz",
"integrity": "sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA=="
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -8652,6 +8700,26 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true "dev": true
}, },
"ngx-markdown": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-10.1.1.tgz",
"integrity": "sha512-bUVgN6asb35d5U4xM5CNfo7pSpuwqJSdTgK0PhNZzLiaiyPIK2owtLF6sWGhxTThJu+LngJPjj4MQ+AFe/s8XQ==",
"requires": {
"@types/marked": "^1.1.0",
"emoji-toolkit": "^6.0.1",
"katex": "^0.12.0",
"marked": "^1.1.0",
"prismjs": "^1.20.0",
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
}
}
},
"ngx-monaco-editor": { "ngx-monaco-editor": {
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/ngx-monaco-editor/-/ngx-monaco-editor-8.1.1.tgz", "resolved": "https://registry.npmjs.org/ngx-monaco-editor/-/ngx-monaco-editor-8.1.1.tgz",
@ -10480,6 +10548,14 @@
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
"dev": true "dev": true
}, },
"prismjs": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.22.0.tgz",
"integrity": "sha512-lLJ/Wt9yy0AiSYBf212kK3mM5L8ycwlyTlSxHBAneXLR0nzFMlZ5y7riFPF3E33zXOF2IH95xdY5jIyZbM9z/w==",
"requires": {
"clipboard": "^2.0.0"
}
},
"process": { "process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -11582,6 +11658,12 @@
} }
} }
}, },
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"select-hose": { "select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@ -12970,6 +13052,12 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true "dev": true
}, },
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"tmp": { "tmp": {
"version": "0.0.33", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

View File

@ -30,6 +30,7 @@
"ag-grid-community": "^22.1.1", "ag-grid-community": "^22.1.1",
"core-js": "^2.5.4", "core-js": "^2.5.4",
"moment": "^2.24.0", "moment": "^2.24.0",
"ngx-markdown": "^10.1.1",
"ngx-monaco-editor": "^8.1.1", "ngx-monaco-editor": "^8.1.1",
"rxjs": "~6.6.3", "rxjs": "~6.6.3",
"tslib": "^1.9.0", "tslib": "^1.9.0",

View File

@ -14,6 +14,7 @@ import { TreeModule } from '@circlon/angular-tree-component';
import {AgGridModule} from 'ag-grid-angular'; import {AgGridModule} from 'ag-grid-angular';
import {MonacoEditorModule} from 'ngx-monaco-editor'; import {MonacoEditorModule} from 'ngx-monaco-editor';
import { APP_BASE_HREF, PlatformLocation } from '@angular/common'; import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
import { MarkdownModule } from 'ngx-markdown';
/** /**
* This function is used internal to get a string instance of the `<base href="" />` value from `index.html`. * This function is used internal to get a string instance of the `<base href="" />` value from `index.html`.
@ -42,6 +43,7 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
TreeModule, TreeModule,
AgGridModule.withComponents([]), AgGridModule.withComponents([]),
MonacoEditorModule.forRoot(), MonacoEditorModule.forRoot(),
MarkdownModule.forRoot(),
], ],
providers: [ providers: [
StatusBar, StatusBar,

View File

@ -27,7 +27,9 @@ import {BooleanRendererComponent} from './editor/database/renderers/boolean-rend
import {SearchComponent} from './search/Search.component'; import {SearchComponent} from './search/Search.component';
import {NormComponent} from './nodes/norm/norm.component'; import {NormComponent} from './nodes/norm/norm.component';
import {MarkdownComponent as MarkdownEditorComponent} from './nodes/markdown/markdown.component';
import {DirectivesModule} from '../directives/directives.module'; import {DirectivesModule} from '../directives/directives.module';
import {MarkdownModule} from "ngx-markdown";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -53,6 +55,7 @@ import {DirectivesModule} from '../directives/directives.module';
SearchComponent, SearchComponent,
NormComponent, NormComponent,
MarkdownEditorComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -63,6 +66,7 @@ import {DirectivesModule} from '../directives/directives.module';
ContenteditableModule, ContenteditableModule,
MonacoEditorModule, MonacoEditorModule,
DirectivesModule, DirectivesModule,
MarkdownModule,
], ],
entryComponents: [ entryComponents: [
NodePickerComponent, NodePickerComponent,
@ -87,6 +91,7 @@ import {DirectivesModule} from '../directives/directives.module';
SearchComponent, SearchComponent,
NormComponent, NormComponent,
MarkdownEditorComponent,
], ],
exports: [ exports: [
NodePickerComponent, NodePickerComponent,
@ -111,6 +116,7 @@ import {DirectivesModule} from '../directives/directives.module';
SearchComponent, SearchComponent,
NormComponent, NormComponent,
MarkdownEditorComponent,
] ]
}) })
export class ComponentsModule {} export class ComponentsModule {}

View File

@ -3,6 +3,10 @@
<i class="fa" slot="start" [ngClass]="typeIcons.node"></i> <i class="fa" slot="start" [ngClass]="typeIcons.node"></i>
<ion-label>Paragraph</ion-label> <ion-label>Paragraph</ion-label>
</ion-item> </ion-item>
<ion-item button (click)="onSelect('markdown')" class="markdown">
<i class="fa" slot="start" [ngClass]="typeIcons.markdown"></i>
<ion-label>Markdown</ion-label>
</ion-item>
<ion-item button (click)="onSelect('database_ref')" class="db"> <ion-item button (click)="onSelect('database_ref')" class="db">
<i class="fa" slot="start" [ngClass]="typeIcons.db"></i> <i class="fa" slot="start" [ngClass]="typeIcons.db"></i>
<ion-label>Database</ion-label> <ion-label>Database</ion-label>

View File

@ -25,3 +25,9 @@ i {
color: var(--noded-background-files); color: var(--noded-background-files);
} }
} }
.markdown {
i {
color: var(--noded-background-markdown);
}
}

View File

@ -0,0 +1,11 @@
<div class="container" (click)="onFocusIn()">
<div class="editor-container" *ngIf="showEditor">
<ngx-monaco-editor class="editor"
[options]="editorOptions"
[(ngModel)]="contents"
(ngModelChange)="onContentsChanged($event)"
#editor
></ngx-monaco-editor>
</div>
<div class="display" markdown katex [data]="contents"></div>
</div>

View File

@ -0,0 +1,23 @@
.container {
display: flex;
min-height: 600px;
flex-direction: row;
.editor-container, .display {
flex: 1;
}
.editor-container {
display: flex;
flex-direction: column;
}
.editor {
border: 1px solid lightgrey;
flex: 1;
}
.display {
margin-left: 10px;
}
}

View File

@ -0,0 +1,77 @@
import {Component, HostListener, Input, OnInit, ViewChild} from '@angular/core';
import {EditorNodeContract} from '../EditorNode.contract';
import {EditorService} from '../../../service/editor.service';
import {v4} from 'uuid';
@Component({
selector: 'editor-markdown',
templateUrl: './markdown.component.html',
styleUrls: ['./markdown.component.scss'],
})
export class MarkdownComponent extends EditorNodeContract implements OnInit {
// @ViewChild('editable') editable;
@Input() nodeId: string;
// public isFocused = false;
public initialValue = 'Click to edit...';
protected savedValue = 'Click to edit...';
public contents = '';
private dirtyOverride = false;
public showEditor = false;
public editorOptions = {
language: 'markdown',
uri: v4(),
readOnly: false,
automaticLayout: true,
};
constructor(
public readonly editorService: EditorService,
) {
super();
this.contents = this.initialValue;
this.savedValue = this.initialValue;
}
ngOnInit() {
this.editorService.registerNodeEditor(this.nodeId, this).then(() => {
if ( !this.node.Value ) {
this.node.Value = {};
}
if ( this.node.Value.Value ) {
this.initialValue = this.node.Value.Value;
this.savedValue = this.node.Value.Value;
}
this.contents = this.initialValue;
});
}
public isDirty(): boolean | Promise<boolean> {
return this.dirtyOverride || this.contents !== this.savedValue;
}
public writeChangesToNode(): void | Promise<void> {
this.node.Value.Mode = 'code';
this.node.Value.Value = this.contents;
this.node.value = this.contents;
this.savedValue = this.contents;
}
onContentsChanged(event) {
if ( event !== this.savedValue ) {
this.editorService.triggerSave();
}
}
onFocusIn() {
this.showEditor = true;
}
@HostListener('document:keyup.escape', ['$event'])
onFocusOut(event) {
this.showEditor = false;
}
}

View File

@ -37,6 +37,9 @@
<ng-container *ngIf="node.isNorm()"> <ng-container *ngIf="node.isNorm()">
<editor-norm style="flex: 1;" [nodeId]="node.UUID"></editor-norm> <editor-norm style="flex: 1;" [nodeId]="node.UUID"></editor-norm>
</ng-container> </ng-container>
<ng-container *ngIf="node.type === 'markdown'">
<editor-markdown style="flex: 1;" [nodeId]="node.UUID"></editor-markdown>
</ng-container>
<ng-container *ngIf="node.type === 'database_ref'"> <ng-container *ngIf="node.type === 'database_ref'">
<editor-database style="flex: 1;" [nodeId]="node.UUID"></editor-database> <editor-database style="flex: 1;" [nodeId]="node.UUID"></editor-database>
</ng-container> </ng-container>

View File

@ -32,6 +32,10 @@ ion-icon.invisible {
&.file_ref { &.file_ref {
color: var(--noded-background-files); color: var(--noded-background-files);
} }
&.markdown {
color: var(--noded-background-markdown);
}
} }
.host-add-button { .host-add-button {

View File

@ -1,6 +1,6 @@
export default class HostRecord { export default class HostRecord {
public value = ''; public value = '';
public type: 'paragraph'|'database_ref'|'code_ref'|'file_ref' = 'paragraph'; public type: 'paragraph'|'database_ref'|'code_ref'|'file_ref'|'markdown' = 'paragraph';
public CreatedAt: string; public CreatedAt: string;
public PageId: string; public PageId: string;

View File

@ -9,4 +9,5 @@ export const NodeTypeIcons = {
code_ref: 'fa fa-code', code_ref: 'fa fa-code',
file_ref: 'fa fa-archive', file_ref: 'fa fa-archive',
files: 'fa fa-archive', files: 'fa fa-archive',
markdown: 'fab fa-markdown',
}; };

View File

@ -29,6 +29,7 @@
@import "~ag-grid-community/dist/styles/ag-grid.css"; @import "~ag-grid-community/dist/styles/ag-grid.css";
@import "~ag-grid-community/dist/styles/ag-theme-balham.css"; @import "~ag-grid-community/dist/styles/ag-theme-balham.css";
@import "~@fortawesome/fontawesome-free/css/all.min.css"; @import "~@fortawesome/fontawesome-free/css/all.min.css";
@import "~@fortawesome/fontawesome-free/css/brands.min.css";
:root { :root {
--noded-background-note: #3A86FF; --noded-background-note: #3A86FF;
@ -50,6 +51,10 @@
--noded-background-files: #0E7B81; --noded-background-files: #0E7B81;
--noded-color-files: white; --noded-color-files: white;
--noded-background-files-hover: #14AFB8; --noded-background-files-hover: #14AFB8;
--noded-background-markdown: #5F4D30;
--noded-color-markdown: white;
--noded-color-markdown-hover: #7A633E;
} }
div.picker-wrapper { div.picker-wrapper {