diff --git a/angular.json b/angular.json index fe4034c..3baab77 100644 --- a/angular.json +++ b/angular.json @@ -48,9 +48,15 @@ }, { "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": { "production": { diff --git a/package-lock.json b/package-lock.json index 70c5219..ec39923 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2126,6 +2126,11 @@ "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "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": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -3636,6 +3641,17 @@ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "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": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -3795,8 +3811,7 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "commondir": { "version": "1.0.1", @@ -4758,6 +4773,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "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": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -4992,6 +5013,11 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -6003,6 +6029,15 @@ "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": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -7830,6 +7865,14 @@ "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": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -8227,6 +8270,11 @@ "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": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -8652,6 +8700,26 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "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": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/ngx-monaco-editor/-/ngx-monaco-editor-8.1.1.tgz", @@ -10480,6 +10548,14 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "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": { "version": "0.11.10", "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -12970,6 +13052,12 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "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": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index a8230b0..e6d4459 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "ag-grid-community": "^22.1.1", "core-js": "^2.5.4", "moment": "^2.24.0", + "ngx-markdown": "^10.1.1", "ngx-monaco-editor": "^8.1.1", "rxjs": "~6.6.3", "tslib": "^1.9.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0cb284f..e7629ad 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,6 +14,7 @@ import { TreeModule } from '@circlon/angular-tree-component'; import {AgGridModule} from 'ag-grid-angular'; import {MonacoEditorModule} from 'ngx-monaco-editor'; 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 `` value from `index.html`. @@ -42,6 +43,7 @@ export function getBaseHref(platformLocation: PlatformLocation): string { TreeModule, AgGridModule.withComponents([]), MonacoEditorModule.forRoot(), + MarkdownModule.forRoot(), ], providers: [ StatusBar, diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index 5d4b644..49632ed 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -27,7 +27,9 @@ import {BooleanRendererComponent} from './editor/database/renderers/boolean-rend import {SearchComponent} from './search/Search.component'; import {NormComponent} from './nodes/norm/norm.component'; +import {MarkdownComponent as MarkdownEditorComponent} from './nodes/markdown/markdown.component'; import {DirectivesModule} from '../directives/directives.module'; +import {MarkdownModule} from "ngx-markdown"; @NgModule({ declarations: [ @@ -53,17 +55,19 @@ import {DirectivesModule} from '../directives/directives.module'; SearchComponent, NormComponent, + MarkdownEditorComponent, ], - imports: [ - CommonModule, - IonicModule, - AgGridModule, - FormsModule, - ReactiveFormsModule, - ContenteditableModule, - MonacoEditorModule, - DirectivesModule, - ], + imports: [ + CommonModule, + IonicModule, + AgGridModule, + FormsModule, + ReactiveFormsModule, + ContenteditableModule, + MonacoEditorModule, + DirectivesModule, + MarkdownModule, + ], entryComponents: [ NodePickerComponent, DatabaseComponent, @@ -87,6 +91,7 @@ import {DirectivesModule} from '../directives/directives.module'; SearchComponent, NormComponent, + MarkdownEditorComponent, ], exports: [ NodePickerComponent, @@ -111,6 +116,7 @@ import {DirectivesModule} from '../directives/directives.module'; SearchComponent, NormComponent, + MarkdownEditorComponent, ] }) export class ComponentsModule {} 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 41df36f..a319230 100644 --- a/src/app/components/editor/node-picker/node-picker.component.html +++ b/src/app/components/editor/node-picker/node-picker.component.html @@ -3,6 +3,10 @@ Paragraph + + + Markdown + Database diff --git a/src/app/components/editor/node-picker/node-picker.component.scss b/src/app/components/editor/node-picker/node-picker.component.scss index a7f2b95..f5582b4 100644 --- a/src/app/components/editor/node-picker/node-picker.component.scss +++ b/src/app/components/editor/node-picker/node-picker.component.scss @@ -25,3 +25,9 @@ i { color: var(--noded-background-files); } } + +.markdown { + i { + color: var(--noded-background-markdown); + } +} diff --git a/src/app/components/nodes/markdown/markdown.component.html b/src/app/components/nodes/markdown/markdown.component.html new file mode 100644 index 0000000..8977e90 --- /dev/null +++ b/src/app/components/nodes/markdown/markdown.component.html @@ -0,0 +1,11 @@ +
+
+ +
+
+
\ No newline at end of file diff --git a/src/app/components/nodes/markdown/markdown.component.scss b/src/app/components/nodes/markdown/markdown.component.scss new file mode 100644 index 0000000..5be88bd --- /dev/null +++ b/src/app/components/nodes/markdown/markdown.component.scss @@ -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; + } +} diff --git a/src/app/components/nodes/markdown/markdown.component.ts b/src/app/components/nodes/markdown/markdown.component.ts new file mode 100644 index 0000000..758ceae --- /dev/null +++ b/src/app/components/nodes/markdown/markdown.component.ts @@ -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 { + return this.dirtyOverride || this.contents !== this.savedValue; + } + + public writeChangesToNode(): void | Promise { + 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; + } +} diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html index 3c0e3ea..0bc96e8 100644 --- a/src/app/pages/editor/editor.page.html +++ b/src/app/pages/editor/editor.page.html @@ -37,6 +37,9 @@ + + + diff --git a/src/app/pages/editor/editor.page.scss b/src/app/pages/editor/editor.page.scss index e5d6a1a..1b6beb4 100644 --- a/src/app/pages/editor/editor.page.scss +++ b/src/app/pages/editor/editor.page.scss @@ -32,6 +32,10 @@ ion-icon.invisible { &.file_ref { color: var(--noded-background-files); } + + &.markdown { + color: var(--noded-background-markdown); + } } .host-add-button { diff --git a/src/app/structures/HostRecord.ts b/src/app/structures/HostRecord.ts index 9fca377..a53453d 100644 --- a/src/app/structures/HostRecord.ts +++ b/src/app/structures/HostRecord.ts @@ -1,6 +1,6 @@ export default class HostRecord { 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 PageId: string; diff --git a/src/app/structures/node-types.ts b/src/app/structures/node-types.ts index e7397c6..c21b1bd 100644 --- a/src/app/structures/node-types.ts +++ b/src/app/structures/node-types.ts @@ -9,4 +9,5 @@ export const NodeTypeIcons = { code_ref: 'fa fa-code', file_ref: 'fa fa-archive', files: 'fa fa-archive', + markdown: 'fab fa-markdown', }; diff --git a/src/global.scss b/src/global.scss index 5e09cc6..e7180d5 100644 --- a/src/global.scss +++ b/src/global.scss @@ -29,6 +29,7 @@ @import "~ag-grid-community/dist/styles/ag-grid.css"; @import "~ag-grid-community/dist/styles/ag-theme-balham.css"; @import "~@fortawesome/fontawesome-free/css/all.min.css"; +@import "~@fortawesome/fontawesome-free/css/brands.min.css"; :root { --noded-background-note: #3A86FF; @@ -50,6 +51,10 @@ --noded-background-files: #0E7B81; --noded-color-files: white; --noded-background-files-hover: #14AFB8; + + --noded-background-markdown: #5F4D30; + --noded-color-markdown: white; + --noded-color-markdown-hover: #7A633E; } div.picker-wrapper {