+
-
diff --git a/ui-ngx/src/app/shared/components/js-func.component.scss b/ui-ngx/src/app/shared/components/js-func.component.scss
index 3a7ddc3d09..6b11058333 100644
--- a/ui-ngx/src/app/shared/components/js-func.component.scss
+++ b/ui-ngx/src/app/shared/components/js-func.component.scss
@@ -24,14 +24,22 @@
height: 100%;
}
+ &:not(.tb-js-func-title) {
+ .tb-js-func-panel {
+ margin-left: 15px;
+ }
+ }
+
+ &.tb-js-func-title {
+ .tb-js-func-panel {
+ height: calc(100% - 40px);
+ }
+ }
+
.tb-js-func-panel {
height: calc(100% - 80px);
border: 1px solid #c0c0c0;
- &:not(.tb-js-func-title) {
- margin-left: 15px;
- }
-
#tb-javascript-input {
width: 100%;
min-width: 200px;
@@ -43,6 +51,12 @@
}
}
+ &:not(.tb-fullscreen) {
+ &.tb-js-func-title {
+ padding-bottom: 15px;
+ }
+ }
+
.tb-js-func-toolbar {
& > * {
&:not(:last-child) {
diff --git a/ui-ngx/src/app/shared/components/markdown-editor.component.html b/ui-ngx/src/app/shared/components/markdown-editor.component.html
new file mode 100644
index 0000000000..e31ad8bc3f
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/markdown-editor.component.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/shared/components/markdown-editor.component.scss b/ui-ngx/src/app/shared/components/markdown-editor.component.scss
new file mode 100644
index 0000000000..a4149c54d7
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/markdown-editor.component.scss
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+.markdown-content {
+ min-width: 400px;
+ &.tb-edit-mode {
+ .tb-markdown-view-container {
+ border: 1px solid #c0c0c0;
+ }
+ .tb-markdown-view {
+ padding: 20px;
+ }
+ &:not(.tb-fullscreen) {
+ padding-bottom: 15px;
+ .markdown-content-editor {
+ min-height: 200px;
+ max-height: 200px;
+ height: 200px;
+ }
+ .tb-markdown-view-container {
+ min-height: 200px;
+ max-height: 200px;
+ height: 200px;
+ }
+ }
+ }
+ &.tb-fullscreen {
+ background: #fff;
+ .markdown-content-editor {
+ height: calc(100% - 40px);
+ }
+ }
+ .markdown-content-editor {
+ position: relative;
+ height: 100%;
+ }
+ .tb-markdown-editor {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border: 1px solid #c0c0c0;
+ }
+ .tb-markdown-view-container {
+ overflow: auto;
+ height: 100%;
+ }
+ .buttons-panel {
+ position: absolute;
+ top: 5px;
+ right: 24px;
+ z-index: 1;
+ button.edit-toggle {
+ min-width: 32px;
+ min-height: 15px;
+ padding: 4px;
+ margin: 0;
+ font-size: .8rem;
+ line-height: 15px;
+ color: #7b7b7b;
+ background: rgba(220, 220, 220, .35);
+ }
+ }
+}
diff --git a/ui-ngx/src/app/shared/components/markdown-editor.component.ts b/ui-ngx/src/app/shared/components/markdown-editor.component.ts
new file mode 100644
index 0000000000..7c1c9c7c02
--- /dev/null
+++ b/ui-ngx/src/app/shared/components/markdown-editor.component.ts
@@ -0,0 +1,154 @@
+///
+/// Copyright © 2016-2022 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { Ace } from 'ace-builds';
+import { coerceBooleanProperty } from '@angular/cdk/coercion';
+import { getAce } from '@shared/models/ace/ace.models';
+
+@Component({
+ selector: 'tb-markdown-editor',
+ templateUrl: './markdown-editor.component.html',
+ styleUrls: ['./markdown-editor.component.scss'],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => MarkdownEditorComponent),
+ multi: true
+ }
+ ]
+})
+export class MarkdownEditorComponent implements OnInit, ControlValueAccessor {
+
+ @Input() label: string;
+
+ @Input() disabled: boolean;
+
+ @Input() readonly: boolean;
+
+ @ViewChild('markdownEditor', {static: true})
+ markdownEditorElmRef: ElementRef;
+
+ private markdownEditor: Ace.Editor;
+
+ editorMode = true;
+
+ fullscreen = false;
+
+ markdownValue: string;
+ renderValue: string;
+
+ ignoreChange = false;
+
+ private propagateChange = null;
+
+ private requiredValue: boolean;
+
+ get required(): boolean {
+ return this.requiredValue;
+ }
+
+ @Input()
+ set required(value: boolean) {
+ this.requiredValue = coerceBooleanProperty(value);
+ }
+
+ constructor() {
+ }
+
+ ngOnInit(): void {
+ if (!this.readonly) {
+ const editorElement = this.markdownEditorElmRef.nativeElement;
+ let editorOptions: Partial
= {
+ mode: 'ace/mode/markdown',
+ showGutter: true,
+ showPrintMargin: false,
+ readOnly: false
+ };
+
+ const advancedOptions = {
+ enableSnippets: true,
+ enableBasicAutocompletion: true,
+ enableLiveAutocompletion: true
+ };
+
+ editorOptions = {...editorOptions, ...advancedOptions};
+
+ getAce().subscribe(
+ (ace) => {
+ this.markdownEditor = ace.edit(editorElement, editorOptions);
+ this.markdownEditor.session.setUseWrapMode(false);
+ this.markdownEditor.setValue(this.markdownValue ? this.markdownValue : '', -1);
+ this.markdownEditor.on('change', () => {
+ if (!this.ignoreChange) {
+ this.updateView();
+ }
+ });
+ }
+ );
+
+ }
+ }
+
+ registerOnChange(fn: any): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ }
+
+ writeValue(value: string): void {
+ this.editorMode = true;
+ this.markdownValue = value;
+ this.renderValue = this.markdownValue ? this.markdownValue : ' ';
+ if (this.markdownEditor) {
+ this.ignoreChange = true;
+ this.markdownEditor.setValue(this.markdownValue ? this.markdownValue : '', -1);
+ this.ignoreChange = false;
+ }
+ }
+
+ updateView() {
+ const editorValue = this.markdownEditor.getValue();
+ if (this.markdownValue !== editorValue) {
+ this.markdownValue = editorValue;
+ this.renderValue = this.markdownValue ? this.markdownValue : ' ';
+ this.propagateChange(this.markdownValue);
+ }
+ }
+
+ onFullscreen() {
+ if (this.markdownEditor) {
+ setTimeout(() => {
+ this.markdownEditor.resize();
+ }, 0);
+ }
+ }
+
+ toggleEditMode() {
+ this.editorMode = !this.editorMode;
+ if (this.editorMode && this.markdownEditor) {
+ setTimeout(() => {
+ this.markdownEditor.resize();
+ }, 0);
+ }
+ }
+}
diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts
index a356fa5921..9d3d6864fe 100644
--- a/ui-ngx/src/app/shared/shared.module.ts
+++ b/ui-ngx/src/app/shared/shared.module.ts
@@ -79,6 +79,7 @@ import { EnumToArrayPipe } from '@shared/pipe/enum-to-array.pipe';
import { ClipboardModule } from 'ngx-clipboard';
import { ValueInputComponent } from '@shared/components/value-input.component';
import { MarkdownModule, MarkedOptions } from 'ngx-markdown';
+import { MarkdownEditorComponent } from '@shared/components/markdown-editor.component';
import { FullscreenDirective } from '@shared/components/fullscreen.directive';
import { HighlightPipe } from '@shared/pipe/highlight.pipe';
import { DashboardAutocompleteComponent } from '@shared/components/dashboard-autocomplete.component';
@@ -258,6 +259,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
KeyValMapComponent,
NavTreeComponent,
LedLightComponent,
+ MarkdownEditorComponent,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,
@@ -452,6 +454,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
KeyValMapComponent,
NavTreeComponent,
LedLightComponent,
+ MarkdownEditorComponent,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,
diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json
index 47d22d9b3f..272ea90d7d 100644
--- a/ui-ngx/src/assets/locale/locale.constant-en_US.json
+++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json
@@ -2390,6 +2390,8 @@
"error": "Login error"
},
"markdown": {
+ "edit": "Edit",
+ "preview": "Preview",
"copy-code": "Click to copy",
"copied": "Copied!"
},
@@ -3368,6 +3370,12 @@
"rotateButton": "Rotate polygon"
}
},
+ "markdown": {
+ "use-markdown-text-function": "Use markdown/HTML value function",
+ "markdown-text-function": "Markdown/HTML value function",
+ "markdown-text-pattern": "Markdown/HTML pattern (markdown or HTML with variables, for ex. '${entityName} or ${keyName} - some text.')",
+ "markdown-css": "Markdown/HTML CSS"
+ },
"table": {
"common-table-settings": "Common Table Settings",
"enable-search": "Enable search",