mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(editor): Inline expression editor (#4814)
* WIP * 🔥 Remove unneeded watch * ⚡ Further setup * ⚡ Fix import * ⚡ Minor tweaks * 🔥 Remove logging * 🎨 Add some styling * 🎨 More styling changes * 🐛 Fix wrong marking of stale data * 🎨 Prevent fx on dragging * 🔥 Remove logging * ⚡ Refine draggable target offsets * refactor(editor): Consolidate expression management logic (#4836) * ⚡ Extract `ExpressionFunctionIcon` * ⚡ Simplify syntax * ⚡ Move to mixin * 🎨 Format * 📘 Unify types * ⚡ Dedup double brace handler * ⚡ Consolidate resolvable highlighter * 🎨 Format * ⚡ Consolidate language pack * ✏️ Add comment * ⚡ Move completions to plugins * ⚡ Partially deduplicate themes * refactor(editor): Apply styling feedback to inline expression editor (#4846) * 🎨 Adjust styling for expression parameter input * 🎨 Style outputs differently * ⚡ Set single line for RLC * 🎨 Style both openers identically * 🐛 Prevent defocus on resize * ⚡ Adjust line height * 🎨 Adjust border with for expression input * ⚡ Fix font family for inline output * ⚡ Set up telemetry * ⚡ Complete telemetry * ⚡ Simplify event source * ⚡ Set monospaced font for inline output * 🎨 Hide cursor on schema pill drop * 🧪 Update snapshots * ⚡ Consolidate editor styles * ✏️ Add tech debt comments * ⚡ Improve naming * ⚡ Improve inside resolvable detection * ⚡ Improve var naming * 🔥 Remove outdated comment * 🚚 Move constant to data * ✏️ Clarify comments * 🔥 Remove outdated comments * 🔥 Remove unneeded try-catch * 🔥 Remove unneeded method * 🔥 Remove unneeded check * 🔥 Remove `openExpression` check * 🔥 Remove unused timeout * 🔥 Remove commented out sections * ⚡ Use Pinia naming convention * ⚡ Re-evaluate on change of `ndvInputData` * 🐛 Fix handling of `0` in number-type input * 🐛 Surface focus and blur for mapping hints * 🔥 Remove logging * ✏️ Reword error * ⚡ Change kebab-case to PascalCase * ⚡ Refactor state fields for clarity * ⚡ Support double bracing on selection * 🎨 More styling * ⚡ Miscellaneous cleanup * ⚡ Disregard error on drop * 🎨 Fix schema pill styling * 🎨 More `background` to `background-color` fixes * 🧪 Update snapshots * 🎨 Replace non-existing var with white * 🧪 Update snapshot * 📦 Integrate `codemirror-lang-n8n-expression` * 🎨 Fix formatting * 🧪 Re-update test snapshots * 🧪 Update selectors for inline editor * 🔥 Remove unused test ID * 📘 Add type for `currentNodePaneType` * ⚡ Refactor mixin to util * ⚡ Use `:global` * 🔥 Remove comment * ⚡ Add watch * ⚡ Change import style * 👕 Fix lint * ⚡ Refactor preventing blur on resize * 🔥 Remove comment * 🧪 Re-update snapshots * 🎨 Prettify * 👕 Fix lint * 🔥 Remove comment Co-authored-by: Mutasem <mutdmour@gmail.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { EditorView, Decoration, DecorationSet } from '@codemirror/view';
|
||||
import { StateField, StateEffect } from '@codemirror/state';
|
||||
import { tags } from '@lezer/highlight';
|
||||
import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
|
||||
|
||||
import type { ColoringStateEffect, Plaintext, Resolvable, Resolved } from '@/types/expressions';
|
||||
|
||||
const cssClasses = {
|
||||
validResolvable: 'cm-valid-resolvable',
|
||||
invalidResolvable: 'cm-invalid-resolvable',
|
||||
brokenResolvable: 'cm-broken-resolvable',
|
||||
plaintext: 'cm-plaintext',
|
||||
};
|
||||
|
||||
const resolvablesTheme = EditorView.theme({
|
||||
['.' + cssClasses.validResolvable]: {
|
||||
color: 'var(--color-valid-resolvable-foreground)',
|
||||
backgroundColor: 'var(--color-valid-resolvable-background)',
|
||||
},
|
||||
['.' + cssClasses.invalidResolvable]: {
|
||||
color: 'var(--color-invalid-resolvable-foreground)',
|
||||
backgroundColor: 'var(--color-invalid-resolvable-background)',
|
||||
},
|
||||
});
|
||||
|
||||
const marks = {
|
||||
valid: Decoration.mark({ class: cssClasses.validResolvable }),
|
||||
invalid: Decoration.mark({ class: cssClasses.invalidResolvable }),
|
||||
};
|
||||
|
||||
const coloringStateEffects = {
|
||||
addColorEffect: StateEffect.define<ColoringStateEffect.Value>({
|
||||
map: ({ from, to, kind, error }, change) => ({
|
||||
from: change.mapPos(from),
|
||||
to: change.mapPos(to),
|
||||
kind,
|
||||
error,
|
||||
}),
|
||||
}),
|
||||
removeColorEffect: StateEffect.define<ColoringStateEffect.Value>({
|
||||
map: ({ from, to }, change) => ({
|
||||
from: change.mapPos(from),
|
||||
to: change.mapPos(to),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
const coloringStateField = StateField.define<DecorationSet>({
|
||||
provide: (stateField) => EditorView.decorations.from(stateField),
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(colorings, transaction) {
|
||||
colorings = colorings.map(transaction.changes); // recalculate positions for new doc
|
||||
|
||||
for (const txEffect of transaction.effects) {
|
||||
if (txEffect.is(coloringStateEffects.removeColorEffect)) {
|
||||
colorings = colorings.update({
|
||||
filter: (from, to) => txEffect.value.from !== from && txEffect.value.to !== to,
|
||||
});
|
||||
}
|
||||
|
||||
if (txEffect.is(coloringStateEffects.addColorEffect)) {
|
||||
colorings = colorings.update({
|
||||
filter: (from, to) => txEffect.value.from !== from && txEffect.value.to !== to,
|
||||
});
|
||||
|
||||
const decoration = txEffect.value.error ? marks.invalid : marks.valid;
|
||||
|
||||
if (txEffect.value.from === 0 && txEffect.value.to === 0) continue;
|
||||
|
||||
colorings = colorings.update({
|
||||
add: [decoration.range(txEffect.value.from, txEffect.value.to)],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return colorings;
|
||||
},
|
||||
});
|
||||
|
||||
function addColor(view: EditorView, segments: Array<Resolvable | Resolved>) {
|
||||
const effects: Array<StateEffect<unknown>> = segments.map(({ from, to, kind, error }) =>
|
||||
coloringStateEffects.addColorEffect.of({ from, to, kind, error }),
|
||||
);
|
||||
|
||||
if (effects.length === 0) return;
|
||||
|
||||
if (!view.state.field(coloringStateField, false)) {
|
||||
effects.push(StateEffect.appendConfig.of([coloringStateField, resolvablesTheme]));
|
||||
}
|
||||
|
||||
view.dispatch({ effects });
|
||||
}
|
||||
|
||||
function removeColor(view: EditorView, segments: Plaintext[]) {
|
||||
const effects: Array<StateEffect<unknown>> = segments.map(({ from, to }) =>
|
||||
coloringStateEffects.removeColorEffect.of({ from, to }),
|
||||
);
|
||||
|
||||
if (effects.length === 0) return;
|
||||
|
||||
if (!view.state.field(coloringStateField, false)) {
|
||||
effects.push(StateEffect.appendConfig.of([coloringStateField, resolvablesTheme]));
|
||||
}
|
||||
|
||||
view.dispatch({ effects });
|
||||
}
|
||||
|
||||
const resolvableStyle = syntaxHighlighting(
|
||||
HighlightStyle.define([
|
||||
{
|
||||
tag: tags.content,
|
||||
class: cssClasses.plaintext,
|
||||
},
|
||||
{
|
||||
tag: tags.className,
|
||||
class: cssClasses.brokenResolvable,
|
||||
},
|
||||
/**
|
||||
* CSS classes for valid and invalid resolvables
|
||||
* dynamically applied based on state fields
|
||||
*/
|
||||
]),
|
||||
);
|
||||
|
||||
export const highlighter = {
|
||||
addColor,
|
||||
removeColor,
|
||||
resolvableStyle,
|
||||
};
|
||||
Reference in New Issue
Block a user