feat(editor): Adjust HTML editor component for use in params (#5285)

*  Adjust HTML editor component

* ♻️ Apply feedback

* ♻️ Apply feedback

* 🔥 Remove unused ndv store refs
This commit is contained in:
Iván Ovejero 2023-02-02 14:00:16 +01:00 committed by GitHub
parent 3b5e1d127f
commit 8b09e98654
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 16 deletions

View file

@ -36,11 +36,20 @@ export default mixins(expressionManager).extend({
props: { props: {
html: { html: {
type: String, type: String,
required: true,
}, },
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
rows: {
type: Number,
default: -1,
},
disableExpressionColoring: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -72,8 +81,8 @@ export default mixins(expressionManager).extend({
EditorView.updateListener.of((viewUpdate: ViewUpdate) => { EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
if (!viewUpdate.docChanged) return; if (!viewUpdate.docChanged) return;
highlighter.removeColor(this.editor, this.htmlSegments); this.getHighlighter()?.removeColor(this.editor, this.htmlSegments);
highlighter.addColor(this.editor, this.resolvableSegments); this.getHighlighter()?.addColor(this.editor, this.resolvableSegments);
this.$emit('valueChanged', this.doc); this.$emit('valueChanged', this.doc);
}), }),
@ -144,7 +153,31 @@ export default mixins(expressionManager).extend({
return root; return root;
}, },
isMissingHtmlTags() {
const zerothSection = this.sections.at(0);
return (
!zerothSection?.content.trim().startsWith('<html') &&
!zerothSection?.content.trim().endsWith('</html>')
);
},
format() { format() {
if (this.sections.length === 1 && this.isMissingHtmlTags()) {
const zerothSection = this.sections.at(0) as Section;
const formatted = prettier
.format(zerothSection.content, {
parser: 'html',
plugins: [htmlParser],
})
.trim();
return this.editor.dispatch({
changes: { from: 0, to: this.doc.length, insert: formatted },
});
}
const formatted = []; const formatted = [];
for (const { kind, content } of this.sections) { for (const { kind, content } of this.sections) {
@ -185,20 +218,34 @@ export default mixins(expressionManager).extend({
} }
} }
if (formatted.length === 0) return;
this.editor.dispatch({ this.editor.dispatch({
changes: { from: 0, to: this.doc.length, insert: formatted.join('\n\n') }, changes: { from: 0, to: this.doc.length, insert: formatted.join('\n\n') },
}); });
}, },
getHighlighter() {
if (this.disableExpressionColoring) return;
return highlighter;
},
}, },
mounted() { mounted() {
htmlEditorEventBus.$on('format-html', this.format); htmlEditorEventBus.$on('format-html', this.format);
const state = EditorState.create({ doc: this.html, extensions: this.extensions }); let doc = this.html;
if (this.html === '' && this.rows > 0) {
doc = '\n'.repeat(this.rows - 1);
}
const state = EditorState.create({ doc, extensions: this.extensions });
this.editor = new EditorView({ parent: this.root(), state }); this.editor = new EditorView({ parent: this.root(), state });
highlighter.addColor(this.editor, this.resolvableSegments); this.getHighlighter()?.addColor(this.editor, this.resolvableSegments);
}, },
destroyed() { destroyed() {

View file

@ -18,6 +18,9 @@ export const theme = [
'.cm-cursor, .cm-dropCursor': { '.cm-cursor, .cm-dropCursor': {
borderLeftColor: 'var(--color-code-caret)', borderLeftColor: 'var(--color-code-caret)',
}, },
'&.cm-editor.cm-focused': {
outline: '0',
},
'&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection': { '&.cm-focused .cm-selectionBackgroundm .cm-selectionBackground, .cm-content ::selection': {
backgroundColor: 'var(--color-code-selection)', backgroundColor: 'var(--color-code-selection)',
}, },
@ -30,6 +33,8 @@ export const theme = [
'.cm-gutters': { '.cm-gutters': {
backgroundColor: 'var(--color-code-gutterBackground)', backgroundColor: 'var(--color-code-gutterBackground)',
color: 'var(--color-code-gutterForeground)', color: 'var(--color-code-gutterForeground)',
borderTopLeftRadius: 'var(--border-radius-base)',
borderBottomLeftRadius: 'var(--border-radius-base)',
}, },
'.cm-scroller': { '.cm-scroller': {
overflow: 'auto', overflow: 'auto',

View file

@ -81,9 +81,11 @@
/> />
<html-editor <html-editor
v-else-if="getArgument('editor') === 'htmlEditor' && isHtmlNode(node)" v-else-if="getArgument('editor') === 'htmlEditor'"
:html="node.parameters.html" :html="node.parameters.html"
:isReadOnly="isReadOnly" :isReadOnly="isReadOnly"
:rows="getArgument('rows')"
:disableExpressionColoring="!isHtmlNode(node)"
@valueChanged="valueChangedDebounced" @valueChanged="valueChangedDebounced"
/> />
@ -353,8 +355,8 @@ import { workflowHelpers } from '@/mixins/workflowHelpers';
import { hasExpressionMapping, isValueExpression, isResourceLocatorValue } from '@/utils'; import { hasExpressionMapping, isValueExpression, isResourceLocatorValue } from '@/utils';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import { CUSTOM_API_CALL_KEY } from '@/constants'; import { CUSTOM_API_CALL_KEY, HTML_NODE_TYPE } from '@/constants';
import { CODE_NODE_TYPE, HTML_NODE_TYPE } from '@/constants'; import { CODE_NODE_TYPE } from '@/constants';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { debounceHelper } from '@/mixins/debounce'; import { debounceHelper } from '@/mixins/debounce';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';

View file

@ -28,9 +28,6 @@
import { NodeParameterValueType } from 'n8n-workflow'; import { NodeParameterValueType } from 'n8n-workflow';
import Vue, { PropType } from 'vue'; import Vue, { PropType } from 'vue';
import { isValueExpression, isResourceLocatorValue } from '@/utils'; import { isValueExpression, isResourceLocatorValue } from '@/utils';
import { useNDVStore } from '@/stores/ndv';
import { mapStores } from 'pinia';
import { HTML_NODE_TYPE } from '@/constants';
export default Vue.extend({ export default Vue.extend({
name: 'parameter-options', name: 'parameter-options',
@ -54,13 +51,15 @@ export default Vue.extend({
}, },
}, },
computed: { computed: {
...mapStores(useNDVStore),
isDefault(): boolean { isDefault(): boolean {
return this.parameter.default === this.value; return this.parameter.default === this.value;
}, },
isValueExpression(): boolean { isValueExpression(): boolean {
return isValueExpression(this.parameter, this.value); return isValueExpression(this.parameter, this.value);
}, },
isHtmlEditor(): boolean {
return this.getArgument('editor') === 'htmlEditor';
},
shouldShowOptions(): boolean { shouldShowOptions(): boolean {
if (this.isReadOnly === true) { if (this.isReadOnly === true) {
return false; return false;
@ -95,13 +94,10 @@ export default Vue.extend({
return !!this.getArgument('loadOptionsMethod') || !!this.getArgument('loadOptions'); return !!this.getArgument('loadOptionsMethod') || !!this.getArgument('loadOptions');
}, },
actions(): Array<{ label: string; value: string; disabled?: boolean }> { actions(): Array<{ label: string; value: string; disabled?: boolean }> {
if ( if (this.isHtmlEditor && !this.isValueExpression) {
this.ndvStore.activeNode?.type === HTML_NODE_TYPE &&
this.ndvStore.activeNode?.parameters.operation === 'generateHtmlTemplate'
) {
return [ return [
{ {
label: 'Format HTML', label: this.$locale.baseText('parameterInput.formatHtml'),
value: 'formatHtml', value: 'formatHtml',
}, },
]; ];

View file

@ -878,6 +878,7 @@
"parameterInput.error": "ERROR", "parameterInput.error": "ERROR",
"parameterInput.expression": "Expression", "parameterInput.expression": "Expression",
"parameterInput.fixed": "Fixed", "parameterInput.fixed": "Fixed",
"parameterInput.formatHtml": "Format HTML",
"parameterInput.issues": "Issues", "parameterInput.issues": "Issues",
"parameterInput.loadingOptions": "Loading options...", "parameterInput.loadingOptions": "Loading options...",
"parameterInput.openEditWindow": "Open Edit Window", "parameterInput.openEditWindow": "Open Edit Window",

View file

@ -85,6 +85,7 @@ export class Mailgun implements INodeType {
type: 'string', type: 'string',
typeOptions: { typeOptions: {
rows: 5, rows: 5,
editor: 'htmlEditor',
}, },
default: '', default: '',
description: 'HTML text message of email', description: 'HTML text message of email',