mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix: Empty onclick breaks range parser in HTML editor (#18032)
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import { Prec, EditorState } from '@codemirror/state';
|
||||
import {
|
||||
dropCursor,
|
||||
EditorView,
|
||||
highlightActiveLine,
|
||||
highlightActiveLineGutter,
|
||||
keymap,
|
||||
@@ -35,7 +36,7 @@ import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
|
||||
import { autoCloseTags, htmlLanguage } from 'codemirror-lang-html-n8n';
|
||||
import { codeEditorTheme } from '../CodeNodeEditor/theme';
|
||||
import type { Range, Section } from './types';
|
||||
import { nonTakenRanges } from './utils';
|
||||
import { nonTakenRanges, pasteHandler } from './utils';
|
||||
import type { TargetNodeParameterContext } from '@/Interface';
|
||||
|
||||
type Props = {
|
||||
@@ -67,6 +68,7 @@ const extensions = computed(() => [
|
||||
]),
|
||||
autoCloseTags,
|
||||
expressionCloseBrackets(),
|
||||
pasteSanitizer(),
|
||||
Prec.highest(keymap.of(editorKeymap)),
|
||||
indentOnInput(),
|
||||
codeEditorTheme({
|
||||
@@ -231,6 +233,12 @@ async function formatHtml() {
|
||||
});
|
||||
}
|
||||
|
||||
function pasteSanitizer() {
|
||||
return EditorView.domEventHandlers({
|
||||
paste: pasteHandler,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
htmlEditorEventBus.on('format-html', formatHtml);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { describe, it, expect, vi } from 'vitest'; // Change to jest if needed
|
||||
import { pasteHandler } from './utils';
|
||||
import type { EditorView } from '@codemirror/view';
|
||||
|
||||
describe('pasteHandler', () => {
|
||||
it('replaces empty onclick attributes and dispatches changes', () => {
|
||||
const clipboardData = {
|
||||
getData: (type: string) => (type === 'text/html' ? '<div onclick=""></div>' : ''),
|
||||
};
|
||||
|
||||
const preventDefault = vi.fn();
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const event = { clipboardData, preventDefault } as unknown as ClipboardEvent;
|
||||
const view = {
|
||||
state: {
|
||||
selection: {
|
||||
main: { from: 0, to: 0 },
|
||||
},
|
||||
},
|
||||
dispatch,
|
||||
} as unknown as EditorView;
|
||||
|
||||
const result = pasteHandler(event, view);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(preventDefault).toHaveBeenCalled();
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: 0,
|
||||
insert: '<div onclick=" "></div>',
|
||||
},
|
||||
scrollIntoView: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing if there is no onclick="" in pasted content', () => {
|
||||
const clipboardData = {
|
||||
getData: (type: string) => (type === 'text/html' ? '<p>Hello world</p>' : ''),
|
||||
};
|
||||
const preventDefault = vi.fn();
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const event = { clipboardData, preventDefault } as unknown as ClipboardEvent;
|
||||
const view = {
|
||||
state: {
|
||||
selection: {
|
||||
main: { from: 0, to: 0 },
|
||||
},
|
||||
},
|
||||
dispatch,
|
||||
} as unknown as EditorView;
|
||||
|
||||
const result = pasteHandler(event, view);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(preventDefault).not.toHaveBeenCalled();
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does nothing if clipboard data is empty', () => {
|
||||
const clipboardData = {
|
||||
getData: () => '',
|
||||
};
|
||||
const preventDefault = vi.fn();
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const event = { clipboardData, preventDefault } as unknown as ClipboardEvent;
|
||||
const view = {
|
||||
state: {
|
||||
selection: {
|
||||
main: { from: 0, to: 0 },
|
||||
},
|
||||
},
|
||||
dispatch,
|
||||
} as unknown as EditorView;
|
||||
|
||||
const result = pasteHandler(event, view);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(preventDefault).not.toHaveBeenCalled();
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Range } from './types';
|
||||
|
||||
import type { EditorView } from '@codemirror/view';
|
||||
|
||||
/**
|
||||
* Return the ranges of a full range that are _not_ within the taken ranges,
|
||||
* assuming sorted taken ranges. e.g. `[0, 10]` and `[[2, 3], [7, 8]]`
|
||||
@@ -38,3 +40,25 @@ export function nonTakenRanges(fullRange: Range, takenRanges: Range[]) {
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
export function pasteHandler(event: ClipboardEvent, view: EditorView) {
|
||||
const htmlData = event.clipboardData?.getData('text/html') ?? '';
|
||||
const textData = event.clipboardData?.getData('text/plain') ?? '';
|
||||
|
||||
const content = htmlData || textData;
|
||||
if (!content) return false;
|
||||
|
||||
// Replace onclick="" with onclick=" " as empty onclick breaks range parser
|
||||
if (/onclick=""/.test(content)) {
|
||||
event.preventDefault();
|
||||
const sanitized = content.replace(/onclick=""/g, 'onclick=" "');
|
||||
const { from, to } = view.state.selection.main;
|
||||
view.dispatch({
|
||||
changes: { from, to, insert: sanitized },
|
||||
scrollIntoView: true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user