mirror of
https://github.com/nocobase/nocobase.git
synced 2026-09-24 16:02:20 +08:00
feat(code-editor): add support for HTML templates in JavaScript
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
"@nocobase/evaluators": "1.9.0-alpha.16",
|
||||
"@nocobase/sdk": "1.9.0-alpha.16",
|
||||
"@nocobase/utils": "1.9.0-alpha.16",
|
||||
"@codemirror/lang-html": "^6.4.10",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.24.2",
|
||||
"antd-style": "3.7.1",
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { javascriptWithHtmlTemplates } from '../javascriptHtmlTemplate';
|
||||
|
||||
describe('javascriptWithHtmlTemplates', () => {
|
||||
it('mounts html parser for template literal segments', () => {
|
||||
const support = javascriptWithHtmlTemplates();
|
||||
const state = EditorState.create({
|
||||
doc: 'const template = `<div class="box">${value}</div>`;',
|
||||
extensions: [support],
|
||||
});
|
||||
|
||||
const htmlPos = state.doc.toString().indexOf('div class');
|
||||
const node = syntaxTree(state).resolveInner(htmlPos, 1);
|
||||
|
||||
expect(node.name).toBe('TagName');
|
||||
});
|
||||
|
||||
it('preserves interpolation nodes from javascript parser', () => {
|
||||
const support = javascriptWithHtmlTemplates();
|
||||
const state = EditorState.create({
|
||||
doc: 'const template = `<div>${value}</div>`;',
|
||||
extensions: [support],
|
||||
});
|
||||
|
||||
const variablePos = state.doc.toString().indexOf('value');
|
||||
const node = syntaxTree(state).resolveInner(variablePos, 1);
|
||||
|
||||
expect(node.name).toBe('VariableName');
|
||||
});
|
||||
});
|
||||
@@ -11,12 +11,12 @@ import React, { useEffect, useRef } from 'react';
|
||||
|
||||
// CodeMirror imports
|
||||
import { autocompletion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { lintGutter } from '@codemirror/lint';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { basicSetup, EditorView } from 'codemirror';
|
||||
import completions from './completions';
|
||||
import { javascriptWithHtmlTemplates } from './javascriptHtmlTemplate';
|
||||
import { createJavaScriptLinter } from './linter';
|
||||
import { InjectableRendingEventTrigger, InjectableRendingEventTriggerProps } from '../decorator';
|
||||
import { Flex } from 'antd';
|
||||
@@ -83,7 +83,7 @@ const InnerCodeEditor: React.FC<CodeEditorProps> = ({
|
||||
|
||||
const extensions = [
|
||||
basicSetup,
|
||||
javascript(),
|
||||
javascriptWithHtmlTemplates(),
|
||||
autocompletion({
|
||||
override: [createCustomCompletion()],
|
||||
closeOnBlur: false,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { html, htmlLanguage } from '@codemirror/lang-html';
|
||||
import { LanguageSupport } from '@codemirror/language';
|
||||
import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { parseMixed } from '@lezer/common';
|
||||
import type { SyntaxNode } from '@lezer/common';
|
||||
|
||||
interface OverlayRange {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
function buildTemplateHtmlRanges(node: SyntaxNode): OverlayRange[] {
|
||||
const ranges: OverlayRange[] = [];
|
||||
const open = node.from + 1;
|
||||
const close = node.to - 1;
|
||||
let cursor = open;
|
||||
|
||||
for (let child = node.firstChild; child; child = child.nextSibling) {
|
||||
if (child.type.name === 'Interpolation') {
|
||||
if (child.from > cursor) {
|
||||
ranges.push({ from: cursor, to: child.from });
|
||||
}
|
||||
cursor = Math.max(cursor, child.to);
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor < close) {
|
||||
ranges.push({ from: cursor, to: close });
|
||||
}
|
||||
|
||||
return ranges.filter((range) => range.to > range.from);
|
||||
}
|
||||
|
||||
export function javascriptWithHtmlTemplates(): LanguageSupport {
|
||||
const baseJavascript = javascript();
|
||||
const htmlSupport = html();
|
||||
|
||||
const mixedLanguage = javascriptLanguage.configure({
|
||||
wrap: parseMixed((node) => {
|
||||
if (node.type.name !== 'TemplateString') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ranges = buildTemplateHtmlRanges(node.node);
|
||||
if (!ranges.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
parser: htmlLanguage.parser,
|
||||
overlay: ranges,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
return new LanguageSupport(mixedLanguage, [baseJavascript.support, htmlSupport.support]);
|
||||
}
|
||||
Reference in New Issue
Block a user