mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 11:12:20 +08:00
feat: improve code editor auto completion
This commit is contained in:
parent
4a7cf4bd9b
commit
bae4f79460
@ -23,67 +23,109 @@ import { createJavaScriptLinter } from './linter';
|
|||||||
const createCustomCompletion = () => {
|
const createCustomCompletion = () => {
|
||||||
const contextVariables = [
|
const contextVariables = [
|
||||||
{
|
{
|
||||||
label: 'ctx.request',
|
label: 'ctx',
|
||||||
type: 'function',
|
type: 'variable',
|
||||||
info: 'Make an API request to NocoBase backend',
|
info: 'Running context with all available APIs and utilities',
|
||||||
detail: '(options: RequestOptions) => Promise<Response>',
|
detail: 'LowcodeContext',
|
||||||
boost: 102,
|
boost: 110,
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'ctx.getModelById',
|
|
||||||
type: 'function',
|
|
||||||
info: 'Get a model instance by its UID',
|
|
||||||
detail: '(uid: string) => FlowModel | null',
|
|
||||||
boost: 101,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ctx.element',
|
label: 'ctx.element',
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
info: 'The DOM element to render into',
|
info: 'The DOM element to render into',
|
||||||
detail: 'HTMLElement',
|
detail: 'HTMLElement',
|
||||||
boost: 100,
|
boost: 109,
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'ctx',
|
|
||||||
type: 'variable',
|
|
||||||
info: 'Running context',
|
|
||||||
detail: 'Context',
|
|
||||||
boost: 99,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ctx.model',
|
label: 'ctx.model',
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
info: 'Current model instance',
|
info: 'Current LowcodeBlockModel instance',
|
||||||
detail: 'FlowModel',
|
detail: 'LowcodeBlockModel',
|
||||||
boost: 98,
|
boost: 108,
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'ctx.resource',
|
|
||||||
type: 'variable',
|
|
||||||
info: 'Current resource instance',
|
|
||||||
detail: 'APIResource',
|
|
||||||
boost: 97,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ctx.requirejs',
|
label: 'ctx.requirejs',
|
||||||
type: 'function',
|
type: 'function',
|
||||||
info: 'Function to load external JavaScript libraries (callback style)',
|
info: 'Function to load external JavaScript libraries (callback style)',
|
||||||
detail: '(modules: string[], callback: Function) => void',
|
detail: '(modules: string[], callback: Function) => void',
|
||||||
boost: 96,
|
boost: 107,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ctx.requireAsync',
|
label: 'ctx.requireAsync',
|
||||||
type: 'function',
|
type: 'function',
|
||||||
info: 'Function to load external JavaScript libraries (async/await style)',
|
info: 'Function to load external JavaScript libraries (async/await style)',
|
||||||
detail: '(modules: string | string[]) => Promise<any>',
|
detail: '(modules: string | string[]) => Promise<any>',
|
||||||
boost: 95,
|
boost: 106,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'ctx.loadCSS',
|
label: 'ctx.loadCSS',
|
||||||
type: 'function',
|
type: 'function',
|
||||||
info: 'Function to load external CSS files',
|
info: 'Function to load external CSS files',
|
||||||
detail: '(url: string) => Promise<void>',
|
detail: '(url: string) => Promise<void>',
|
||||||
boost: 94,
|
boost: 105,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.getModelById',
|
||||||
|
type: 'function',
|
||||||
|
info: 'Get a model instance by its UID',
|
||||||
|
detail: '(uid: string) => FlowModel | null',
|
||||||
|
boost: 104,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.request',
|
||||||
|
type: 'function',
|
||||||
|
info: 'Make an API request to NocoBase backend',
|
||||||
|
detail: '(options: RequestOptions) => Promise<Response>',
|
||||||
|
boost: 103,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.i18n',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'Internationalization object for translations',
|
||||||
|
detail: 'I18n',
|
||||||
|
boost: 102,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.router',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'Router instance for navigation',
|
||||||
|
detail: 'Router',
|
||||||
|
boost: 101,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.initResource',
|
||||||
|
type: 'function',
|
||||||
|
info: 'Initialize a resource instance with API client',
|
||||||
|
detail: '(use: typeof APIResource, options?: any) => APIResource',
|
||||||
|
boost: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Resources',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'Available resource classes',
|
||||||
|
detail: '{ APIResource, BaseRecordResource, SingleRecordResource, MultiRecordResource }',
|
||||||
|
boost: 99,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.React',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'React library for creating components',
|
||||||
|
detail: 'React',
|
||||||
|
boost: 98,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.ReactDOM',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'ReactDOM library for rendering React components',
|
||||||
|
detail: 'ReactDOM',
|
||||||
|
boost: 97,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Components',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'Available UI component libraries',
|
||||||
|
detail: '{ antd }',
|
||||||
|
boost: 96,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -117,71 +159,302 @@ const createCustomCompletion = () => {
|
|||||||
detail: '(name: string, value: string) => void',
|
detail: '(name: string, value: string) => void',
|
||||||
boost: 87,
|
boost: 87,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.i18n.t',
|
||||||
|
type: 'method',
|
||||||
|
info: 'Translate text using i18n',
|
||||||
|
detail: '(key: string, options?: any) => string',
|
||||||
|
boost: 86,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.router.push',
|
||||||
|
type: 'method',
|
||||||
|
info: 'Navigate to a new route',
|
||||||
|
detail: '(path: string) => void',
|
||||||
|
boost: 85,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Resources.APIResource',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Base API resource class',
|
||||||
|
detail: 'class APIResource',
|
||||||
|
boost: 84,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Resources.BaseRecordResource',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Base record resource class',
|
||||||
|
detail: 'class BaseRecordResource',
|
||||||
|
boost: 83,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Resources.SingleRecordResource',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Single record resource class',
|
||||||
|
detail: 'class SingleRecordResource',
|
||||||
|
boost: 82,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Resources.MultiRecordResource',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Multi record resource class',
|
||||||
|
detail: 'class MultiRecordResource',
|
||||||
|
boost: 81,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Components.antd',
|
||||||
|
type: 'variable',
|
||||||
|
info: 'Ant Design component library',
|
||||||
|
detail: 'AntDesign',
|
||||||
|
boost: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Components.antd.Button',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Ant Design Button component',
|
||||||
|
detail: 'React.Component',
|
||||||
|
boost: 79,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Components.antd.Card',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Ant Design Card component',
|
||||||
|
detail: 'React.Component',
|
||||||
|
boost: 78,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Components.antd.Input',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Ant Design Input component',
|
||||||
|
detail: 'React.Component',
|
||||||
|
boost: 77,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.Components.antd.Table',
|
||||||
|
type: 'class',
|
||||||
|
info: 'Ant Design Table component',
|
||||||
|
detail: 'React.Component',
|
||||||
|
boost: 76,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.React.createElement',
|
||||||
|
type: 'method',
|
||||||
|
info: 'Create a React element',
|
||||||
|
detail: '(type: string | Component, props?: object, ...children: any[]) => ReactElement',
|
||||||
|
boost: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.React.useState',
|
||||||
|
type: 'method',
|
||||||
|
info: 'React useState hook',
|
||||||
|
detail: '(initialState: any) => [state, setState]',
|
||||||
|
boost: 74,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.React.useEffect',
|
||||||
|
type: 'method',
|
||||||
|
info: 'React useEffect hook',
|
||||||
|
detail: '(effect: () => void, deps?: any[]) => void',
|
||||||
|
boost: 73,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ctx.ReactDOM.createRoot',
|
||||||
|
type: 'method',
|
||||||
|
info: 'Create a React root for rendering',
|
||||||
|
detail: '(container: Element) => Root',
|
||||||
|
boost: 72,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'document.createElement',
|
label: 'document.createElement',
|
||||||
type: 'method',
|
type: 'method',
|
||||||
info: 'Create a new HTML element',
|
info: 'Create a new HTML element',
|
||||||
detail: '(tagName: string) => HTMLElement',
|
detail: '(tagName: string) => HTMLElement',
|
||||||
boost: 85,
|
boost: 71,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 代码片段模板
|
// 代码片段模板
|
||||||
const codeSnippets = [
|
const codeSnippets = [
|
||||||
{
|
{
|
||||||
label: 'load-library-async',
|
label: 'basic-html',
|
||||||
type: 'snippet',
|
type: 'snippet',
|
||||||
info: 'Load external library using requireAsync',
|
info: 'Basic HTML content template',
|
||||||
detail: 'Template',
|
detail: 'Template',
|
||||||
boost: 80,
|
boost: 85,
|
||||||
apply: `ctx.requirejs.config({
|
apply: `ctx.element.innerHTML = \`
|
||||||
paths: {
|
<div style="padding: 20px;">
|
||||||
'libraryName': 'https://cdn.jsdelivr.net/npm/library@version/dist/library.min'
|
<h2>Hello World</h2>
|
||||||
}
|
<p>This is a basic HTML template.</p>
|
||||||
});
|
</div>
|
||||||
|
\`;`,
|
||||||
const library = await requireAsync('libraryName');`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'load-css',
|
label: 'react-component',
|
||||||
|
type: 'snippet',
|
||||||
|
info: 'Create and render a React component',
|
||||||
|
detail: 'Template',
|
||||||
|
boost: 84,
|
||||||
|
apply: `const { React, ReactDOM } = ctx;
|
||||||
|
const { Button, Card } = ctx.Components.antd;
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [count, setCount] = React.useState(0);
|
||||||
|
|
||||||
|
return React.createElement(Card, {
|
||||||
|
title: 'React Component',
|
||||||
|
style: { width: 300 }
|
||||||
|
}, [
|
||||||
|
React.createElement('p', { key: 'text' }, \`Count: \${count}\`),
|
||||||
|
React.createElement(Button, {
|
||||||
|
key: 'button',
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => setCount(count + 1)
|
||||||
|
}, 'Increment')
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(ctx.element);
|
||||||
|
root.render(React.createElement(MyComponent));`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'i18n-translation',
|
||||||
|
type: 'snippet',
|
||||||
|
info: 'Use internationalization for text translation',
|
||||||
|
detail: 'Template',
|
||||||
|
boost: 83,
|
||||||
|
apply: `ctx.element.innerHTML = \`
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h2>\${ctx.i18n.t('Welcome')}</h2>
|
||||||
|
<p>\${ctx.i18n.t('This is a translated message')}</p>
|
||||||
|
<button onclick="alert('\${ctx.i18n.t('Button clicked')}')">\${ctx.i18n.t('Click me')}</button>
|
||||||
|
</div>
|
||||||
|
\`;`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'resource-api',
|
||||||
|
type: 'snippet',
|
||||||
|
info: 'Use resource API for data operations',
|
||||||
|
detail: 'Template',
|
||||||
|
boost: 82,
|
||||||
|
apply: `const resource = ctx.initResource(ctx.Resources.MultiRecordResource, {
|
||||||
|
collection: 'users'
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await resource.list({
|
||||||
|
pageSize: 10,
|
||||||
|
sort: ['-createdAt']
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.element.innerHTML = \`
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h3>Users (\${data.meta?.count || 0})</h3>
|
||||||
|
<ul>
|
||||||
|
\${data.data.map(user => \`<li>\${user.nickname || user.email}</li>\`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
} catch (error) {
|
||||||
|
ctx.element.innerHTML = \`<div style="color: red;">Error: \${error.message}</div>\`;
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'async-library-load',
|
||||||
|
type: 'snippet',
|
||||||
|
info: 'Load external library asynchronously',
|
||||||
|
detail: 'Template',
|
||||||
|
boost: 81,
|
||||||
|
apply: `try {
|
||||||
|
// Load external library
|
||||||
|
const library = await ctx.requireAsync('https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js');
|
||||||
|
|
||||||
|
// Use the library
|
||||||
|
const data = library.range(1, 10);
|
||||||
|
ctx.element.innerHTML = \`<pre>\${JSON.stringify(data, null, 2)}</pre>\`;
|
||||||
|
} catch (error) {
|
||||||
|
ctx.element.innerHTML = \`<div style="color: red;">Error loading library: \${error.message}</div>\`;
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'css-load',
|
||||||
type: 'snippet',
|
type: 'snippet',
|
||||||
info: 'Load external CSS file',
|
info: 'Load external CSS file',
|
||||||
detail: 'Template',
|
detail: 'Template',
|
||||||
boost: 79,
|
boost: 80,
|
||||||
apply: `await ctx.loadCSS('https://example.com/styles.css');`,
|
apply: `try {
|
||||||
|
// Load external CSS
|
||||||
|
await ctx.loadCSS('https://cdn.jsdelivr.net/npm/animate.css@4.1.1/animate.min.css');
|
||||||
|
|
||||||
|
// Apply animation
|
||||||
|
ctx.element.innerHTML = \`
|
||||||
|
<div class="animate__animated animate__bounce" style="padding: 20px; text-align: center;">
|
||||||
|
<h3>Content Loaded!</h3>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
} catch (error) {
|
||||||
|
ctx.element.innerHTML = \`<div style="color: red;">Error loading CSS: \${error.message}</div>\`;
|
||||||
|
}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'create-element',
|
label: 'model-communication',
|
||||||
type: 'snippet',
|
type: 'snippet',
|
||||||
info: 'Create and append HTML element',
|
info: 'Communicate with other models',
|
||||||
|
detail: 'Template',
|
||||||
|
boost: 79,
|
||||||
|
apply: `// Get another model by ID
|
||||||
|
const otherModel = ctx.getModelById('some-model-id');
|
||||||
|
|
||||||
|
if (otherModel) {
|
||||||
|
// Access other model's data
|
||||||
|
const otherData = otherModel.getProps();
|
||||||
|
|
||||||
|
ctx.element.innerHTML = \`
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<h3>Model Communication</h3>
|
||||||
|
<p>Current model ID: \${ctx.model.uid}</p>
|
||||||
|
<p>Other model data: \${JSON.stringify(otherData)}</p>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
} else {
|
||||||
|
ctx.element.innerHTML = \`
|
||||||
|
<div style="padding: 20px; color: orange;">
|
||||||
|
<p>Other model not found</p>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'navigation',
|
||||||
|
type: 'snippet',
|
||||||
|
info: 'Navigate to different routes',
|
||||||
detail: 'Template',
|
detail: 'Template',
|
||||||
boost: 78,
|
boost: 78,
|
||||||
apply: `const newElement = document.createElement('div');
|
apply: `ctx.element.innerHTML = \`
|
||||||
newElement.innerHTML = 'Hello World';
|
<div style="padding: 20px;">
|
||||||
element.appendChild(newElement);`,
|
<h3>Navigation Example</h3>
|
||||||
},
|
<button id="nav-home">Go to Home</button>
|
||||||
{
|
<button id="nav-admin" style="margin-left: 10px;">Go to Admin</button>
|
||||||
label: 'async-example',
|
</div>
|
||||||
type: 'snippet',
|
\`;
|
||||||
info: 'Async operation with loading state',
|
|
||||||
detail: 'Template',
|
|
||||||
boost: 77,
|
|
||||||
apply: `element.innerHTML = '<div>Loading...</div>';
|
|
||||||
|
|
||||||
// Simulate async operation
|
// Add event listeners
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
ctx.element.querySelector('#nav-home').addEventListener('click', () => {
|
||||||
|
ctx.router.push('/');
|
||||||
|
});
|
||||||
|
|
||||||
element.innerHTML = '<h3>Content Loaded!</h3>';`,
|
ctx.element.querySelector('#nav-admin').addEventListener('click', () => {
|
||||||
|
ctx.router.push('/admin');
|
||||||
|
});`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'nocobase-api-request',
|
label: 'nocobase-api-request',
|
||||||
type: 'snippet',
|
type: 'snippet',
|
||||||
info: 'Request data from NocoBase API',
|
info: 'Request data from NocoBase API',
|
||||||
detail: 'Template',
|
detail: 'Template',
|
||||||
boost: 76,
|
boost: 77,
|
||||||
apply: `try {
|
apply: `try {
|
||||||
const response = await ctx.request({
|
const response = await ctx.request({
|
||||||
url: 'collection:list',
|
url: 'collections:list',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: {
|
params: {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
@ -190,9 +463,9 @@ element.innerHTML = '<h3>Content Loaded!</h3>';`,
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = response.data?.data || [];
|
const data = response.data?.data || [];
|
||||||
element.innerHTML = \`<pre>\${JSON.stringify(data, null, 2)}</pre>\`;
|
ctx.element.innerHTML = \`<pre>\${JSON.stringify(data, null, 2)}</pre>\`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
element.innerHTML = \`<div style="color: red;">Error: \${error.message}</div>\`;
|
ctx.element.innerHTML = \`<div style="color: red;">Error: \${error.message}</div>\`;
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user