@coding-script/script-engine 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -11
- package/dist/components/format-icon.d.ts +4 -0
- package/dist/components/format-icon.js +52 -0
- package/dist/components/fullscreen-icon.d.ts +9 -0
- package/dist/components/fullscreen-icon.js +68 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/main-function-section.js +97 -4
- package/dist/components/toolbar.d.ts +8 -0
- package/dist/components/toolbar.js +28 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/script-code.js +133 -32
- package/dist/type-panel/type-panel.d.ts +2 -2
- package/dist/type-panel/type-panel.js +2 -2
- package/dist/types/index.d.ts +21 -7
- package/dist/utils/groovy-formatter.d.ts +105 -0
- package/dist/utils/groovy-formatter.js +239 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,23 +1,247 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://www.npmjs.com/package/@coding-script/script-engine)
|
|
2
|
+
# Script Engine
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
基于 React + CodeMirror 6 的 Groovy 脚本编辑器组件库,提供语法高亮、动态类型自动补全、属性面板等功能。
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## 功能特性
|
|
7
|
+
|
|
8
|
+
- **Groovy 语法高亮**:基于 CodeMirror 6,支持完整的 Groovy/Java 语法着色
|
|
9
|
+
- **动态类型自动补全**:基于 `ScriptMetadata` 提供变量名补全和点号链式访问补全(如 `request.test.name`)
|
|
10
|
+
- **Groovy 语法提示**:内置 `if`/`for`/`while`/`println`/`return` 等常用 Groovy 语法片段
|
|
11
|
+
- **代码格式化**:内置 Groovy 格式化器,也可通过 `onFormat` 自定义格式化逻辑
|
|
12
|
+
- **属性面板**:右侧侧边栏展示主函数签名、变量、数据类型(字段和方法),支持折叠/展开和拖拽调节宽度
|
|
13
|
+
- **主题切换**:暗色/亮色两套主题,编辑器、补全弹窗、属性面板同步切换,编辑器内部管理主题状态
|
|
14
|
+
- **全屏模式**:支持 CSS 全屏(`position: fixed` 覆盖视口),不影响编辑器内容
|
|
15
|
+
- **自定义工具栏**:通过 `toolbarExtra` 传入任意 React 节点
|
|
16
|
+
- **热更新**:主题和 metadata 变化时通过 Compartment 热更新,不重建编辑器,不丢失用户输入
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
6
19
|
|
|
7
20
|
```bash
|
|
8
|
-
|
|
21
|
+
npm install @coding-script/script-engine
|
|
22
|
+
# 或
|
|
23
|
+
pnpm add @coding-script/script-engine
|
|
9
24
|
```
|
|
10
25
|
|
|
11
|
-
##
|
|
26
|
+
## 基础用法
|
|
12
27
|
|
|
13
|
-
|
|
28
|
+
```tsx
|
|
29
|
+
import { ScriptCodeEditor } from '@coding-script/script-engine';
|
|
30
|
+
import type { ScriptMetadata } from '@coding-script/script-engine';
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
const metadata: ScriptMetadata = {
|
|
33
|
+
mainMethod: 'run',
|
|
34
|
+
description: '脚本主入口,接收请求参数并返回执行结果状态码',
|
|
35
|
+
returnType: 'Integer',
|
|
36
|
+
binds: [
|
|
37
|
+
{ dataType: 'GroovyBindObject', name: '$request' },
|
|
38
|
+
],
|
|
39
|
+
requests: [
|
|
40
|
+
{ dataType: 'MyScriptRequest', description: '请求参数', name: 'request' },
|
|
41
|
+
],
|
|
42
|
+
types: {
|
|
43
|
+
MyScriptRequest: {
|
|
44
|
+
dataType: 'MyScriptRequest',
|
|
45
|
+
description: '请求参数类型',
|
|
46
|
+
fields: [
|
|
47
|
+
{ dataType: 'int', description: '总数量', name: 'count' },
|
|
48
|
+
{ dataType: 'MyTest', description: '测试对象', name: 'test' },
|
|
49
|
+
],
|
|
50
|
+
functions: [
|
|
51
|
+
{
|
|
52
|
+
name: 'isSupport',
|
|
53
|
+
description: '是否匹配',
|
|
54
|
+
parameters: [{ dataType: 'int', description: '数量', name: 'count' }],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
MyTest: {
|
|
59
|
+
dataType: 'MyTest',
|
|
60
|
+
description: '测试对象',
|
|
61
|
+
fields: [
|
|
62
|
+
{ dataType: 'Long', description: 'id', name: 'id' },
|
|
63
|
+
{ dataType: 'String', description: '名称', name: 'name' },
|
|
64
|
+
],
|
|
65
|
+
functions: [],
|
|
66
|
+
},
|
|
67
|
+
Integer: { dataType: 'Integer', fields: [], functions: [] },
|
|
68
|
+
String: { dataType: 'String', fields: [], functions: [] },
|
|
69
|
+
Long: { dataType: 'Long', fields: [], functions: [] },
|
|
70
|
+
int: { dataType: 'int', fields: [], functions: [] },
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function App() {
|
|
75
|
+
return (
|
|
76
|
+
<ScriptCodeEditor
|
|
77
|
+
value="def run(request){\n return request.count;\n}\n"
|
|
78
|
+
title="Groovy 脚本编辑器"
|
|
79
|
+
defaultTheme="dark"
|
|
80
|
+
metadata={metadata}
|
|
81
|
+
enableThemeToggle
|
|
82
|
+
enableFormat
|
|
83
|
+
enableCompile
|
|
84
|
+
enableFullscreen
|
|
85
|
+
onThemeChange={(theme) => console.log('主题切换:', theme)}
|
|
86
|
+
onChange={(code) => console.log('代码变化:', code)}
|
|
87
|
+
onCompile={(code) => console.log('编译验证:', code)}
|
|
88
|
+
options={{ minHeight: 400, maxHeight: 500 }}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
17
92
|
```
|
|
18
93
|
|
|
19
|
-
|
|
94
|
+
## Props
|
|
95
|
+
|
|
96
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `value` | `string` | `undefined` | 代码内容 |
|
|
99
|
+
| `readonly` | `boolean` | `false` | 是否只读 |
|
|
100
|
+
| `onChange` | `(value: string) => void` | `undefined` | 代码变化回调 |
|
|
101
|
+
| `placeholder` | `string` | `'请输入 Groovy 脚本...'` | 空内容占位符 |
|
|
102
|
+
| `defaultTheme` | `'dark' \| 'light'` | `'dark'` | 初始主题,编辑器内部管理状态 |
|
|
103
|
+
| `onThemeChange` | `(theme: 'dark' \| 'light') => void` | `undefined` | 主题切换通知回调 |
|
|
104
|
+
| `title` | `string` | `undefined` | 工具栏标题 |
|
|
105
|
+
| `metadata` | `ScriptMetadata` | `undefined` | 脚本元数据,提供后启用属性面板和自动补全 |
|
|
106
|
+
| `defaultSidebarOpen` | `boolean` | `metadata != null` | 属性面板默认是否展开 |
|
|
107
|
+
|
|
108
|
+
### 工具栏按钮控制
|
|
109
|
+
|
|
110
|
+
所有工具栏按钮默认**禁用**,需通过 `enable*` 标志显式开启:
|
|
111
|
+
|
|
112
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `enableThemeToggle` | `boolean` | `false` | 是否显示主题切换按钮 |
|
|
115
|
+
| `enableFormat` | `boolean` | `false` | 是否显示格式化按钮(需配合 `onFormat`) |
|
|
116
|
+
| `enableCompile` | `boolean` | `false` | 是否显示编译验证按钮(需配合 `onCompile`) |
|
|
117
|
+
| `enableFullscreen` | `boolean` | `false` | 是否显示全屏按钮 |
|
|
118
|
+
|
|
119
|
+
### 按钮回调
|
|
120
|
+
|
|
121
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| `onFormat` | `() => void` | `undefined` | 格式化代码回调(不提供时使用内置 Groovy 格式化器) |
|
|
124
|
+
| `onCompile` | `(code: string) => void` | `undefined` | 编译/测试脚本回调 |
|
|
125
|
+
|
|
126
|
+
### 自定义工具栏
|
|
127
|
+
|
|
128
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
129
|
+
|---|---|---|---|
|
|
130
|
+
| `toolbarExtra` | `React.ReactNode` | `undefined` | 工具栏额外内容,渲染在内置按钮之后,可传入任意 JSX |
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<ScriptCodeEditor
|
|
134
|
+
toolbarExtra={
|
|
135
|
+
<>
|
|
136
|
+
<button onClick={save}>💾 保存</button>
|
|
137
|
+
<button onClick={help}>❓ 帮助</button>
|
|
138
|
+
</>
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 布局选项
|
|
144
|
+
|
|
145
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| `options.fontSize` | `number` | `14` | 字体大小(px) |
|
|
148
|
+
| `options.minHeight` | `number` | `300` | 编辑器最小高度(px) |
|
|
149
|
+
| `options.maxHeight` | `number` | `300` | 编辑器最大高度(px) |
|
|
150
|
+
|
|
151
|
+
## ScriptMetadata 数据结构
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
interface ScriptMetadata {
|
|
155
|
+
/** 主函数名称(可选) */
|
|
156
|
+
mainMethod?: string;
|
|
157
|
+
/** 主函数描述信息(可选,提供后在属性面板主函数名旁显示悬浮提示) */
|
|
158
|
+
description?: string;
|
|
159
|
+
/** 注入变量(如 $request,name 含 $ 前缀) */
|
|
160
|
+
binds: ScriptBindInfo[];
|
|
161
|
+
/** 主函数参数 */
|
|
162
|
+
requests: ScriptRequestInfo[];
|
|
163
|
+
/** 主函数返回类型(可选) */
|
|
164
|
+
returnType?: string;
|
|
165
|
+
/** 所有可用类型定义(含基础类型如 Integer/String) */
|
|
166
|
+
types: Record<string, ScriptTypeInfo>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface ScriptTypeInfo {
|
|
170
|
+
dataType: string;
|
|
171
|
+
description?: string;
|
|
172
|
+
fields: ScriptFieldInfo[];
|
|
173
|
+
functions: ScriptFunctionInfo[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface ScriptFieldInfo {
|
|
177
|
+
name: string;
|
|
178
|
+
dataType: string;
|
|
179
|
+
description?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface ScriptFunctionInfo {
|
|
183
|
+
name: string;
|
|
184
|
+
parameters: ScriptParameterInfo[];
|
|
185
|
+
description?: string;
|
|
186
|
+
returnType?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface ScriptParameterInfo {
|
|
190
|
+
name: string;
|
|
191
|
+
dataType: string;
|
|
192
|
+
description?: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface ScriptBindInfo {
|
|
196
|
+
name: string;
|
|
197
|
+
dataType: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface ScriptRequestInfo {
|
|
202
|
+
name: string;
|
|
203
|
+
dataType: string;
|
|
204
|
+
description?: string;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> **注意**:`metadata` 必须是解析后的 JavaScript 对象,不能是 JSON 字符串。如果从 API 获取的是 JSON 字符串,需要先 `JSON.parse()` 再传入。
|
|
209
|
+
|
|
210
|
+
## 自动补全
|
|
211
|
+
|
|
212
|
+
提供 metadata 后,编辑器支持以下补全能力:
|
|
213
|
+
|
|
214
|
+
| 输入 | 补全内容 |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `re` | 弹出 `request`、`$request` 等变量 |
|
|
217
|
+
| `request.` | 弹出 `count`、`test`、`isSupport` 等字段和方法 |
|
|
218
|
+
| `request.test.` | 弹出 `id`、`name` 等链式访问成员 |
|
|
219
|
+
| `if` / `for` / `while` | 弹出 Groovy 语法片段(含 tab-stop 占位符) |
|
|
220
|
+
|
|
221
|
+
不提供 metadata 时,仅启用 Groovy 关键字和语法片段补全。
|
|
222
|
+
|
|
223
|
+
## 本地开发
|
|
20
224
|
|
|
21
225
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
226
|
+
# 安装依赖
|
|
227
|
+
pnpm install
|
|
228
|
+
|
|
229
|
+
# 启动库 watch 模式(终端 1)
|
|
230
|
+
pnpm run watch:script-engine
|
|
231
|
+
|
|
232
|
+
# 启动演示应用(终端 2)
|
|
233
|
+
pnpm run dev:app-pc
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
演示应用访问 http://localhost:3000
|
|
237
|
+
|
|
238
|
+
## 技术栈
|
|
239
|
+
|
|
240
|
+
- **编辑器**:[CodeMirror 6](https://codemirror.net/)(`@codemirror/view`、`state`、`autocomplete`、`lang-java`、`theme-one-dark`)
|
|
241
|
+
- **构建工具**:[Rslib](https://rslib.rs/)(库)+ [Rsbuild](https://rsbuild.dev/)(演示应用)
|
|
242
|
+
- **包管理**:pnpm monorepo(workspaces)
|
|
243
|
+
- **UI**:纯 CSS-in-JS(React `style` 对象),库本身不依赖 Ant Design
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
Apache-2.0 license
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import react from "react";
|
|
2
|
+
const FormatIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
|
|
3
|
+
width: "14",
|
|
4
|
+
height: "14",
|
|
5
|
+
viewBox: "0 0 24 24",
|
|
6
|
+
fill: "none",
|
|
7
|
+
style: {
|
|
8
|
+
flexShrink: 0
|
|
9
|
+
}
|
|
10
|
+
}, /*#__PURE__*/ react.createElement("rect", {
|
|
11
|
+
x: "4",
|
|
12
|
+
y: "2",
|
|
13
|
+
width: "16",
|
|
14
|
+
height: "20",
|
|
15
|
+
rx: "2",
|
|
16
|
+
stroke: color,
|
|
17
|
+
strokeWidth: "1.5",
|
|
18
|
+
fill: "none"
|
|
19
|
+
}), /*#__PURE__*/ react.createElement("line", {
|
|
20
|
+
x1: "7",
|
|
21
|
+
y1: "7",
|
|
22
|
+
x2: "14",
|
|
23
|
+
y2: "7",
|
|
24
|
+
stroke: color,
|
|
25
|
+
strokeWidth: "1.5",
|
|
26
|
+
strokeLinecap: "round"
|
|
27
|
+
}), /*#__PURE__*/ react.createElement("line", {
|
|
28
|
+
x1: "7",
|
|
29
|
+
y1: "10.5",
|
|
30
|
+
x2: "17",
|
|
31
|
+
y2: "10.5",
|
|
32
|
+
stroke: color,
|
|
33
|
+
strokeWidth: "1.5",
|
|
34
|
+
strokeLinecap: "round"
|
|
35
|
+
}), /*#__PURE__*/ react.createElement("line", {
|
|
36
|
+
x1: "9",
|
|
37
|
+
y1: "14",
|
|
38
|
+
x2: "15",
|
|
39
|
+
y2: "14",
|
|
40
|
+
stroke: color,
|
|
41
|
+
strokeWidth: "1.5",
|
|
42
|
+
strokeLinecap: "round"
|
|
43
|
+
}), /*#__PURE__*/ react.createElement("line", {
|
|
44
|
+
x1: "7",
|
|
45
|
+
y1: "17.5",
|
|
46
|
+
x2: "12",
|
|
47
|
+
y2: "17.5",
|
|
48
|
+
stroke: color,
|
|
49
|
+
strokeWidth: "1.5",
|
|
50
|
+
strokeLinecap: "round"
|
|
51
|
+
}));
|
|
52
|
+
export { FormatIcon };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import react from "react";
|
|
2
|
+
const MaximizeIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
|
|
3
|
+
width: "14",
|
|
4
|
+
height: "14",
|
|
5
|
+
viewBox: "0 0 24 24",
|
|
6
|
+
fill: "none",
|
|
7
|
+
style: {
|
|
8
|
+
flexShrink: 0
|
|
9
|
+
}
|
|
10
|
+
}, /*#__PURE__*/ react.createElement("path", {
|
|
11
|
+
d: "M4 9V4h5",
|
|
12
|
+
stroke: color,
|
|
13
|
+
strokeWidth: "2",
|
|
14
|
+
strokeLinecap: "round",
|
|
15
|
+
strokeLinejoin: "round"
|
|
16
|
+
}), /*#__PURE__*/ react.createElement("path", {
|
|
17
|
+
d: "M20 9V4h-5",
|
|
18
|
+
stroke: color,
|
|
19
|
+
strokeWidth: "2",
|
|
20
|
+
strokeLinecap: "round",
|
|
21
|
+
strokeLinejoin: "round"
|
|
22
|
+
}), /*#__PURE__*/ react.createElement("path", {
|
|
23
|
+
d: "M4 15v5h5",
|
|
24
|
+
stroke: color,
|
|
25
|
+
strokeWidth: "2",
|
|
26
|
+
strokeLinecap: "round",
|
|
27
|
+
strokeLinejoin: "round"
|
|
28
|
+
}), /*#__PURE__*/ react.createElement("path", {
|
|
29
|
+
d: "M20 15v5h-5",
|
|
30
|
+
stroke: color,
|
|
31
|
+
strokeWidth: "2",
|
|
32
|
+
strokeLinecap: "round",
|
|
33
|
+
strokeLinejoin: "round"
|
|
34
|
+
}));
|
|
35
|
+
const MinimizeIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
|
|
36
|
+
width: "14",
|
|
37
|
+
height: "14",
|
|
38
|
+
viewBox: "0 0 24 24",
|
|
39
|
+
fill: "none",
|
|
40
|
+
style: {
|
|
41
|
+
flexShrink: 0
|
|
42
|
+
}
|
|
43
|
+
}, /*#__PURE__*/ react.createElement("path", {
|
|
44
|
+
d: "M9 4v5H4",
|
|
45
|
+
stroke: color,
|
|
46
|
+
strokeWidth: "2",
|
|
47
|
+
strokeLinecap: "round",
|
|
48
|
+
strokeLinejoin: "round"
|
|
49
|
+
}), /*#__PURE__*/ react.createElement("path", {
|
|
50
|
+
d: "M15 4v5h5",
|
|
51
|
+
stroke: color,
|
|
52
|
+
strokeWidth: "2",
|
|
53
|
+
strokeLinecap: "round",
|
|
54
|
+
strokeLinejoin: "round"
|
|
55
|
+
}), /*#__PURE__*/ react.createElement("path", {
|
|
56
|
+
d: "M9 20v-5H4",
|
|
57
|
+
stroke: color,
|
|
58
|
+
strokeWidth: "2",
|
|
59
|
+
strokeLinecap: "round",
|
|
60
|
+
strokeLinejoin: "round"
|
|
61
|
+
}), /*#__PURE__*/ react.createElement("path", {
|
|
62
|
+
d: "M15 20v-5h5",
|
|
63
|
+
stroke: color,
|
|
64
|
+
strokeWidth: "2",
|
|
65
|
+
strokeLinecap: "round",
|
|
66
|
+
strokeLinejoin: "round"
|
|
67
|
+
}));
|
|
68
|
+
export { MaximizeIcon, MinimizeIcon };
|
|
@@ -26,3 +26,4 @@ export { VariablesSection } from './variables-section';
|
|
|
26
26
|
export type { VariablesSectionProps } from './variables-section';
|
|
27
27
|
export { DataTypesSection } from './data-types-section';
|
|
28
28
|
export type { DataTypesSectionProps } from './data-types-section';
|
|
29
|
+
export { MaximizeIcon, MinimizeIcon } from './fullscreen-icon';
|
package/dist/components/index.js
CHANGED
|
@@ -12,3 +12,4 @@ export { TypeSection } from "./type-section.js";
|
|
|
12
12
|
export { MainFunctionSection } from "./main-function-section.js";
|
|
13
13
|
export { VariablesSection } from "./variables-section.js";
|
|
14
14
|
export { DataTypesSection } from "./data-types-section.js";
|
|
15
|
+
export { MaximizeIcon, MinimizeIcon } from "./fullscreen-icon.js";
|
|
@@ -1,6 +1,51 @@
|
|
|
1
|
-
import react from "react";
|
|
1
|
+
import react, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { SectionHeader } from "./section-header.js";
|
|
3
|
-
const
|
|
3
|
+
const TOOLTIP_MARGIN = 6;
|
|
4
|
+
const TOOLTIP_MAX_WIDTH = 380;
|
|
5
|
+
function calcTooltipPos(anchorRect, tooltipEl) {
|
|
6
|
+
const vw = window.innerWidth;
|
|
7
|
+
const vh = window.innerHeight;
|
|
8
|
+
const tooltipH = tooltipEl?.scrollHeight ?? 200;
|
|
9
|
+
const spaceBelow = vh - anchorRect.bottom - TOOLTIP_MARGIN;
|
|
10
|
+
const spaceAbove = anchorRect.top - TOOLTIP_MARGIN;
|
|
11
|
+
const placeBelow = spaceBelow >= Math.min(tooltipH, 260) || spaceBelow >= spaceAbove;
|
|
12
|
+
const top = placeBelow ? anchorRect.bottom + TOOLTIP_MARGIN : Math.max(TOOLTIP_MARGIN, anchorRect.top - tooltipH - TOOLTIP_MARGIN);
|
|
13
|
+
let left = anchorRect.left;
|
|
14
|
+
const maxRight = vw - TOOLTIP_MARGIN;
|
|
15
|
+
if (left + TOOLTIP_MAX_WIDTH > maxRight) left = Math.max(TOOLTIP_MARGIN, maxRight - TOOLTIP_MAX_WIDTH);
|
|
16
|
+
const maxWidth = Math.min(TOOLTIP_MAX_WIDTH, maxRight - left);
|
|
17
|
+
const maxHeight = placeBelow ? spaceBelow - TOOLTIP_MARGIN : spaceAbove - TOOLTIP_MARGIN;
|
|
18
|
+
return {
|
|
19
|
+
top,
|
|
20
|
+
left,
|
|
21
|
+
maxWidth,
|
|
22
|
+
maxHeight: Math.max(maxHeight, 80)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const MainFunctionSection = ({ metadata, colors })=>{
|
|
26
|
+
const [showTip, setShowTip] = useState(false);
|
|
27
|
+
const [pos, setPos] = useState(null);
|
|
28
|
+
const anchorRef = useRef(null);
|
|
29
|
+
const tooltipRef = useRef(null);
|
|
30
|
+
const updatePos = useCallback(()=>{
|
|
31
|
+
if (!anchorRef.current) return;
|
|
32
|
+
const rect = anchorRef.current.getBoundingClientRect();
|
|
33
|
+
setPos(calcTooltipPos(rect, tooltipRef.current));
|
|
34
|
+
}, []);
|
|
35
|
+
useEffect(()=>{
|
|
36
|
+
if (!showTip) return;
|
|
37
|
+
updatePos();
|
|
38
|
+
window.addEventListener('scroll', updatePos, true);
|
|
39
|
+
window.addEventListener('resize', updatePos);
|
|
40
|
+
return ()=>{
|
|
41
|
+
window.removeEventListener('scroll', updatePos, true);
|
|
42
|
+
window.removeEventListener('resize', updatePos);
|
|
43
|
+
};
|
|
44
|
+
}, [
|
|
45
|
+
showTip,
|
|
46
|
+
updatePos
|
|
47
|
+
]);
|
|
48
|
+
return /*#__PURE__*/ react.createElement("div", null, /*#__PURE__*/ react.createElement(SectionHeader, {
|
|
4
49
|
colors: colors,
|
|
5
50
|
label: "主函数"
|
|
6
51
|
}), /*#__PURE__*/ react.createElement("div", {
|
|
@@ -25,7 +70,30 @@ const MainFunctionSection = ({ metadata, colors })=>/*#__PURE__*/ react.createEl
|
|
|
25
70
|
color: colors.text,
|
|
26
71
|
fontWeight: 500
|
|
27
72
|
}
|
|
28
|
-
}, metadata.mainMethod), /*#__PURE__*/ react.createElement("span", {
|
|
73
|
+
}, metadata.mainMethod), metadata.description && /*#__PURE__*/ react.createElement("span", {
|
|
74
|
+
ref: anchorRef,
|
|
75
|
+
style: {
|
|
76
|
+
display: 'inline-flex',
|
|
77
|
+
cursor: 'help',
|
|
78
|
+
flexShrink: 0
|
|
79
|
+
},
|
|
80
|
+
onMouseEnter: ()=>setShowTip(true),
|
|
81
|
+
onMouseLeave: ()=>setShowTip(false)
|
|
82
|
+
}, /*#__PURE__*/ react.createElement("span", {
|
|
83
|
+
style: {
|
|
84
|
+
display: 'inline-flex',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
justifyContent: 'center',
|
|
87
|
+
width: 14,
|
|
88
|
+
height: 14,
|
|
89
|
+
borderRadius: '50%',
|
|
90
|
+
border: `1px solid ${colors.textSecondary}`,
|
|
91
|
+
color: colors.textSecondary,
|
|
92
|
+
fontSize: 10,
|
|
93
|
+
lineHeight: 1,
|
|
94
|
+
fontWeight: 600
|
|
95
|
+
}
|
|
96
|
+
}, "?")), /*#__PURE__*/ react.createElement("span", {
|
|
29
97
|
style: {
|
|
30
98
|
color: colors.textSecondary,
|
|
31
99
|
fontSize: 11
|
|
@@ -36,5 +104,30 @@ const MainFunctionSection = ({ metadata, colors })=>/*#__PURE__*/ react.createEl
|
|
|
36
104
|
fontSize: 11,
|
|
37
105
|
marginLeft: 'auto'
|
|
38
106
|
}
|
|
39
|
-
}, "→ ", metadata.returnType)))
|
|
107
|
+
}, "→ ", metadata.returnType))), showTip && pos && metadata.description && /*#__PURE__*/ react.createElement("div", {
|
|
108
|
+
ref: tooltipRef,
|
|
109
|
+
onMouseEnter: ()=>setShowTip(true),
|
|
110
|
+
onMouseLeave: ()=>setShowTip(false),
|
|
111
|
+
style: {
|
|
112
|
+
position: 'fixed',
|
|
113
|
+
top: pos.top,
|
|
114
|
+
left: pos.left,
|
|
115
|
+
maxWidth: pos.maxWidth,
|
|
116
|
+
maxHeight: pos.maxHeight,
|
|
117
|
+
overflowY: 'auto',
|
|
118
|
+
padding: '8px 12px',
|
|
119
|
+
background: colors.headerBg,
|
|
120
|
+
border: `1px solid ${colors.border}`,
|
|
121
|
+
borderRadius: 6,
|
|
122
|
+
color: colors.text,
|
|
123
|
+
fontSize: 12,
|
|
124
|
+
lineHeight: 1.65,
|
|
125
|
+
whiteSpace: 'pre-wrap',
|
|
126
|
+
wordBreak: 'break-word',
|
|
127
|
+
zIndex: 9999,
|
|
128
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
|
|
129
|
+
pointerEvents: 'auto'
|
|
130
|
+
}
|
|
131
|
+
}, metadata.description));
|
|
132
|
+
};
|
|
40
133
|
export { MainFunctionSection };
|
|
@@ -3,6 +3,14 @@ export interface ToolbarProps {
|
|
|
3
3
|
title?: string;
|
|
4
4
|
theme: 'dark' | 'light';
|
|
5
5
|
onThemeChange?: (theme: 'dark' | 'light') => void;
|
|
6
|
+
enableThemeToggle?: boolean;
|
|
7
|
+
enableFormat?: boolean;
|
|
8
|
+
onFormat?: () => void;
|
|
9
|
+
enableCompile?: boolean;
|
|
6
10
|
onCompile?: () => void;
|
|
11
|
+
enableFullscreen?: boolean;
|
|
12
|
+
isFullscreen?: boolean;
|
|
13
|
+
onToggleFullscreen?: () => void;
|
|
14
|
+
toolbarExtra?: React.ReactNode;
|
|
7
15
|
}
|
|
8
16
|
export declare const Toolbar: React.FC<ToolbarProps>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import react from "react";
|
|
2
2
|
import { ToolbarButton } from "./toolbar-button.js";
|
|
3
|
+
import { FormatIcon } from "./format-icon.js";
|
|
4
|
+
import { MaximizeIcon, MinimizeIcon } from "./fullscreen-icon.js";
|
|
3
5
|
const HammerIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
|
|
4
6
|
width: "14",
|
|
5
7
|
height: "14",
|
|
@@ -26,7 +28,7 @@ const HammerIcon = ({ color })=>/*#__PURE__*/ react.createElement("svg", {
|
|
|
26
28
|
opacity: 0.8,
|
|
27
29
|
transform: "rotate(-25 12.25 14.5)"
|
|
28
30
|
}));
|
|
29
|
-
const Toolbar = ({ title, theme, onThemeChange, onCompile })=>{
|
|
31
|
+
const Toolbar = ({ title, theme, onThemeChange, enableThemeToggle, enableFormat, onFormat, enableCompile, onCompile, enableFullscreen, isFullscreen, onToggleFullscreen, toolbarExtra })=>{
|
|
30
32
|
const isDark = 'dark' === theme;
|
|
31
33
|
const borderColor = isDark ? '#434343' : '#d9d9d9';
|
|
32
34
|
const toolbarBg = isDark ? '#282c34' : '#fafafa';
|
|
@@ -62,7 +64,7 @@ const Toolbar = ({ title, theme, onThemeChange, onCompile })=>{
|
|
|
62
64
|
style: {
|
|
63
65
|
marginRight: 'auto'
|
|
64
66
|
}
|
|
65
|
-
}), /*#__PURE__*/ react.createElement(ToolbarButton, {
|
|
67
|
+
}), enableThemeToggle && /*#__PURE__*/ react.createElement(ToolbarButton, {
|
|
66
68
|
label: isDark ? '🌞 浅色模式' : '🌙 深色模式',
|
|
67
69
|
title: isDark ? '切换到浅色主题' : '切换到深色主题',
|
|
68
70
|
bg: btnBg,
|
|
@@ -70,7 +72,17 @@ const Toolbar = ({ title, theme, onThemeChange, onCompile })=>{
|
|
|
70
72
|
color: toolbarText,
|
|
71
73
|
border: borderColor,
|
|
72
74
|
onClick: handleThemeToggle
|
|
73
|
-
}),
|
|
75
|
+
}), enableFormat && onFormat && /*#__PURE__*/ react.createElement(ToolbarButton, {
|
|
76
|
+
label: /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(FormatIcon, {
|
|
77
|
+
color: isDark ? '#61afef' : '#1677ff'
|
|
78
|
+
}), "格式化"),
|
|
79
|
+
title: "格式化代码",
|
|
80
|
+
bg: isDark ? '#1e3a5f' : '#e6f4ff',
|
|
81
|
+
hoverBg: isDark ? '#264a73' : '#bae0ff',
|
|
82
|
+
color: isDark ? '#61afef' : '#1677ff',
|
|
83
|
+
border: isDark ? '#1e3a5f' : '#91caff',
|
|
84
|
+
onClick: onFormat
|
|
85
|
+
}), enableCompile && onCompile && /*#__PURE__*/ react.createElement(ToolbarButton, {
|
|
74
86
|
label: /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(HammerIcon, {
|
|
75
87
|
color: isDark ? '#98c379' : '#389e0d'
|
|
76
88
|
}), "编译验证"),
|
|
@@ -80,6 +92,18 @@ const Toolbar = ({ title, theme, onThemeChange, onCompile })=>{
|
|
|
80
92
|
color: isDark ? '#98c379' : '#389e0d',
|
|
81
93
|
border: isDark ? '#2d5a27' : '#b7eb8f',
|
|
82
94
|
onClick: onCompile
|
|
83
|
-
})
|
|
95
|
+
}), enableFullscreen && /*#__PURE__*/ react.createElement(ToolbarButton, {
|
|
96
|
+
label: /*#__PURE__*/ react.createElement(react.Fragment, null, isFullscreen ? /*#__PURE__*/ react.createElement(MinimizeIcon, {
|
|
97
|
+
color: isDark ? '#c678dd' : '#722ed1'
|
|
98
|
+
}) : /*#__PURE__*/ react.createElement(MaximizeIcon, {
|
|
99
|
+
color: isDark ? '#c678dd' : '#722ed1'
|
|
100
|
+
}), isFullscreen ? '退出全屏' : '全屏模式'),
|
|
101
|
+
title: isFullscreen ? '退出全屏(ESC)' : '全屏显示',
|
|
102
|
+
bg: isDark ? '#3a2d5a' : '#f3e8ff',
|
|
103
|
+
hoverBg: isDark ? '#4a3d6a' : '#e9d5ff',
|
|
104
|
+
color: isDark ? '#c678dd' : '#722ed1',
|
|
105
|
+
border: isDark ? '#3a2d5a' : '#d3adf7',
|
|
106
|
+
onClick: ()=>onToggleFullscreen?.()
|
|
107
|
+
}), toolbarExtra);
|
|
84
108
|
};
|
|
85
109
|
export { Toolbar };
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/script-code.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createGroovyCompletionSource, createGroovyKeywordSource } from "./autoc
|
|
|
11
11
|
import { TypePanel } from "./type-panel/index.js";
|
|
12
12
|
import { Toolbar } from "./components/toolbar.js";
|
|
13
13
|
import { ExpandSidebarButton } from "./components/expand-sidebar-button.js";
|
|
14
|
+
import { GroovyFormatter } from "./utils/groovy-formatter.js";
|
|
14
15
|
const darkHighlightStyle = HighlightStyle.define([
|
|
15
16
|
{
|
|
16
17
|
tag: tags.keyword,
|
|
@@ -146,23 +147,86 @@ function buildAutocompleteExt(metadata) {
|
|
|
146
147
|
icons: true
|
|
147
148
|
});
|
|
148
149
|
}
|
|
150
|
+
function buildLayoutExtensions(fontSize, minHeight, maxHeight, isFullscreen) {
|
|
151
|
+
if (isFullscreen) return EditorView.theme({
|
|
152
|
+
'&': {
|
|
153
|
+
fontSize: `${fontSize}px`,
|
|
154
|
+
height: '100%'
|
|
155
|
+
},
|
|
156
|
+
'.cm-scroller': {
|
|
157
|
+
overflow: 'auto',
|
|
158
|
+
flex: '1',
|
|
159
|
+
minHeight: '0'
|
|
160
|
+
},
|
|
161
|
+
'.cm-content': {
|
|
162
|
+
fontFamily: 'monospace'
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return EditorView.theme({
|
|
166
|
+
'&': {
|
|
167
|
+
fontSize: `${fontSize}px`
|
|
168
|
+
},
|
|
169
|
+
'.cm-scroller': {
|
|
170
|
+
overflow: 'auto',
|
|
171
|
+
minHeight: `${minHeight}px`,
|
|
172
|
+
maxHeight: `${maxHeight}px`
|
|
173
|
+
},
|
|
174
|
+
'.cm-content': {
|
|
175
|
+
fontFamily: 'monospace',
|
|
176
|
+
minHeight: `${minHeight}px`
|
|
177
|
+
},
|
|
178
|
+
'.cm-gutters': {
|
|
179
|
+
minHeight: `${minHeight}px`
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
149
183
|
const ScriptCodeEditor = (props)=>{
|
|
150
|
-
const { value, readonly = false, onChange, onCompile, onThemeChange, placeholder = '请输入 Groovy 脚本...',
|
|
184
|
+
const { value, readonly = false, onChange, onCompile, onFormat, onThemeChange, placeholder = '请输入 Groovy 脚本...', defaultTheme, title, metadata, defaultSidebarOpen, enableThemeToggle, enableFormat, enableCompile, enableFullscreen, toolbarExtra, options = {} } = props;
|
|
151
185
|
const { fontSize = 14, minHeight = 300, maxHeight = 300 } = options;
|
|
186
|
+
const [internalTheme, setInternalTheme] = useState(defaultTheme ?? 'dark');
|
|
187
|
+
useEffect(()=>{
|
|
188
|
+
if (void 0 !== defaultTheme) setInternalTheme(defaultTheme);
|
|
189
|
+
}, [
|
|
190
|
+
defaultTheme
|
|
191
|
+
]);
|
|
152
192
|
const editorContainerRef = useRef(null);
|
|
153
193
|
const viewRef = useRef(null);
|
|
154
194
|
const themeCompartmentRef = useRef(new Compartment());
|
|
155
195
|
const autocompleteCompartmentRef = useRef(new Compartment());
|
|
196
|
+
const layoutCompartmentRef = useRef(new Compartment());
|
|
156
197
|
const onChangeRef = useRef(onChange);
|
|
157
198
|
onChangeRef.current = onChange;
|
|
158
199
|
const metadataRef = useRef(metadata);
|
|
159
200
|
metadataRef.current = metadata;
|
|
160
201
|
const [sidebarOpen, setSidebarOpen] = useState(defaultSidebarOpen ?? null != metadata);
|
|
161
202
|
const [panelWidth, setPanelWidth] = useState(300);
|
|
203
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
204
|
+
useEffect(()=>{
|
|
205
|
+
if (isFullscreen) {
|
|
206
|
+
const prev = document.body.style.overflow;
|
|
207
|
+
document.body.style.overflow = 'hidden';
|
|
208
|
+
return ()=>{
|
|
209
|
+
document.body.style.overflow = prev;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}, [
|
|
213
|
+
isFullscreen
|
|
214
|
+
]);
|
|
215
|
+
useEffect(()=>{
|
|
216
|
+
if (!isFullscreen) return;
|
|
217
|
+
const handleKeyDown = (e)=>{
|
|
218
|
+
if ('Escape' === e.key) setIsFullscreen(false);
|
|
219
|
+
};
|
|
220
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
221
|
+
return ()=>document.removeEventListener('keydown', handleKeyDown);
|
|
222
|
+
}, [
|
|
223
|
+
isFullscreen
|
|
224
|
+
]);
|
|
162
225
|
useEffect(()=>{
|
|
163
226
|
if (!editorContainerRef.current) return;
|
|
164
227
|
const themeCompartment = themeCompartmentRef.current;
|
|
165
228
|
const acCompartment = autocompleteCompartmentRef.current;
|
|
229
|
+
const layoutCompartment = layoutCompartmentRef.current;
|
|
166
230
|
const extensions = [
|
|
167
231
|
lineNumbers(),
|
|
168
232
|
highlightActiveLineGutter(),
|
|
@@ -189,24 +253,8 @@ const ScriptCodeEditor = (props)=>{
|
|
|
189
253
|
EditorView.updateListener.of((update)=>{
|
|
190
254
|
if (update.docChanged && onChangeRef.current) onChangeRef.current(update.state.doc.toString());
|
|
191
255
|
}),
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
fontSize: `${fontSize}px`
|
|
195
|
-
},
|
|
196
|
-
'.cm-scroller': {
|
|
197
|
-
overflow: 'auto',
|
|
198
|
-
minHeight: `${minHeight}px`,
|
|
199
|
-
maxHeight: `${maxHeight}px`
|
|
200
|
-
},
|
|
201
|
-
'.cm-content': {
|
|
202
|
-
fontFamily: 'monospace',
|
|
203
|
-
minHeight: `${minHeight}px`
|
|
204
|
-
},
|
|
205
|
-
'.cm-gutters': {
|
|
206
|
-
minHeight: `${minHeight}px`
|
|
207
|
-
}
|
|
208
|
-
}),
|
|
209
|
-
themeCompartment.of(buildThemeExtensions(theme)),
|
|
256
|
+
layoutCompartment.of(buildLayoutExtensions(fontSize, minHeight, maxHeight, false)),
|
|
257
|
+
themeCompartment.of(buildThemeExtensions(internalTheme)),
|
|
210
258
|
acCompartment.of(buildAutocompleteExt(metadataRef.current))
|
|
211
259
|
];
|
|
212
260
|
if (readonly) extensions.push(EditorState.readOnly.of(true));
|
|
@@ -243,10 +291,10 @@ const ScriptCodeEditor = (props)=>{
|
|
|
243
291
|
useEffect(()=>{
|
|
244
292
|
if (!viewRef.current) return;
|
|
245
293
|
viewRef.current.dispatch({
|
|
246
|
-
effects: themeCompartmentRef.current.reconfigure(buildThemeExtensions(
|
|
294
|
+
effects: themeCompartmentRef.current.reconfigure(buildThemeExtensions(internalTheme))
|
|
247
295
|
});
|
|
248
296
|
}, [
|
|
249
|
-
|
|
297
|
+
internalTheme
|
|
250
298
|
]);
|
|
251
299
|
useEffect(()=>{
|
|
252
300
|
if (!viewRef.current) return;
|
|
@@ -256,30 +304,80 @@ const ScriptCodeEditor = (props)=>{
|
|
|
256
304
|
}, [
|
|
257
305
|
metadata
|
|
258
306
|
]);
|
|
259
|
-
|
|
307
|
+
useEffect(()=>{
|
|
308
|
+
if (!viewRef.current) return;
|
|
309
|
+
viewRef.current.dispatch({
|
|
310
|
+
effects: layoutCompartmentRef.current.reconfigure(buildLayoutExtensions(fontSize, minHeight, maxHeight, isFullscreen))
|
|
311
|
+
});
|
|
312
|
+
requestAnimationFrame(()=>{
|
|
313
|
+
viewRef.current?.requestMeasure();
|
|
314
|
+
});
|
|
315
|
+
}, [
|
|
316
|
+
isFullscreen,
|
|
317
|
+
fontSize,
|
|
318
|
+
minHeight,
|
|
319
|
+
maxHeight
|
|
320
|
+
]);
|
|
321
|
+
const isDark = 'dark' === internalTheme;
|
|
260
322
|
const borderColor = isDark ? '#434343' : '#d9d9d9';
|
|
261
323
|
const expandBtnBg = isDark ? '#2c313a' : '#f0f0f0';
|
|
262
324
|
const expandBtnColor = isDark ? '#abb2bf' : '#666';
|
|
263
325
|
const handleCompile = ()=>{
|
|
264
326
|
if (onCompile && viewRef.current) onCompile(viewRef.current.state.doc.toString());
|
|
265
327
|
};
|
|
328
|
+
const handleFormat = ()=>{
|
|
329
|
+
if (viewRef.current) {
|
|
330
|
+
const code = viewRef.current.state.doc.toString();
|
|
331
|
+
const formatted = GroovyFormatter.formatScript(code);
|
|
332
|
+
if (formatted !== code) viewRef.current.dispatch({
|
|
333
|
+
changes: {
|
|
334
|
+
from: 0,
|
|
335
|
+
to: viewRef.current.state.doc.length,
|
|
336
|
+
insert: formatted
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
};
|
|
266
341
|
return /*#__PURE__*/ react.createElement("div", {
|
|
267
|
-
style: {
|
|
342
|
+
style: isFullscreen ? {
|
|
343
|
+
position: 'fixed',
|
|
344
|
+
top: 0,
|
|
345
|
+
left: 0,
|
|
346
|
+
right: 0,
|
|
347
|
+
bottom: 0,
|
|
348
|
+
zIndex: 9999,
|
|
349
|
+
backgroundColor: isDark ? '#21252b' : '#ffffff',
|
|
350
|
+
display: 'flex',
|
|
351
|
+
flexDirection: 'column'
|
|
352
|
+
} : {
|
|
268
353
|
display: 'flex',
|
|
269
354
|
flexDirection: 'column'
|
|
270
355
|
}
|
|
271
356
|
}, /*#__PURE__*/ react.createElement(Toolbar, {
|
|
272
357
|
title: title,
|
|
273
|
-
theme:
|
|
274
|
-
onThemeChange:
|
|
275
|
-
|
|
358
|
+
theme: internalTheme,
|
|
359
|
+
onThemeChange: (next)=>{
|
|
360
|
+
setInternalTheme(next);
|
|
361
|
+
onThemeChange?.(next);
|
|
362
|
+
},
|
|
363
|
+
enableThemeToggle: enableThemeToggle,
|
|
364
|
+
enableFormat: enableFormat,
|
|
365
|
+
onFormat: readonly ? void 0 : onFormat ?? handleFormat,
|
|
366
|
+
enableCompile: enableCompile,
|
|
367
|
+
onCompile: onCompile ? handleCompile : void 0,
|
|
368
|
+
enableFullscreen: enableFullscreen,
|
|
369
|
+
isFullscreen: isFullscreen,
|
|
370
|
+
onToggleFullscreen: ()=>setIsFullscreen((prev)=>!prev),
|
|
371
|
+
toolbarExtra: toolbarExtra
|
|
276
372
|
}), /*#__PURE__*/ react.createElement("div", {
|
|
277
373
|
style: {
|
|
278
374
|
display: 'flex',
|
|
279
375
|
border: `1px solid ${borderColor}`,
|
|
280
376
|
borderTop: 'none',
|
|
281
|
-
borderRadius: '0 0 6px 6px',
|
|
282
|
-
overflow: 'hidden'
|
|
377
|
+
borderRadius: isFullscreen ? 0 : '0 0 6px 6px',
|
|
378
|
+
overflow: 'hidden',
|
|
379
|
+
flex: isFullscreen ? 1 : void 0,
|
|
380
|
+
minHeight: isFullscreen ? 0 : void 0
|
|
283
381
|
}
|
|
284
382
|
}, /*#__PURE__*/ react.createElement("div", {
|
|
285
383
|
style: {
|
|
@@ -293,12 +391,15 @@ const ScriptCodeEditor = (props)=>{
|
|
|
293
391
|
expandBtnBg: expandBtnBg,
|
|
294
392
|
onClick: ()=>setSidebarOpen(true)
|
|
295
393
|
}), /*#__PURE__*/ react.createElement("div", {
|
|
296
|
-
ref: editorContainerRef
|
|
394
|
+
ref: editorContainerRef,
|
|
395
|
+
style: isFullscreen ? {
|
|
396
|
+
height: '100%'
|
|
397
|
+
} : void 0
|
|
297
398
|
})), metadata && sidebarOpen && /*#__PURE__*/ react.createElement(TypePanel, {
|
|
298
399
|
metadata: metadata,
|
|
299
|
-
theme:
|
|
300
|
-
minHeight: minHeight,
|
|
301
|
-
maxHeight: maxHeight,
|
|
400
|
+
theme: internalTheme,
|
|
401
|
+
minHeight: isFullscreen ? void 0 : minHeight,
|
|
402
|
+
maxHeight: isFullscreen ? void 0 : maxHeight,
|
|
302
403
|
width: panelWidth,
|
|
303
404
|
onWidthChange: setPanelWidth,
|
|
304
405
|
onCollapse: ()=>setSidebarOpen(false)
|
|
@@ -3,8 +3,8 @@ import type { ScriptMetadata } from '../types';
|
|
|
3
3
|
export interface TypePanelProps {
|
|
4
4
|
metadata: ScriptMetadata;
|
|
5
5
|
theme: 'dark' | 'light';
|
|
6
|
-
minHeight
|
|
7
|
-
maxHeight
|
|
6
|
+
minHeight?: number;
|
|
7
|
+
maxHeight?: number;
|
|
8
8
|
width: number;
|
|
9
9
|
onWidthChange: (width: number) => void;
|
|
10
10
|
onCollapse: () => void;
|
|
@@ -78,8 +78,8 @@ const TypePanel = ({ metadata, theme, minHeight, maxHeight, width, onWidthChange
|
|
|
78
78
|
display: 'flex',
|
|
79
79
|
flexDirection: 'row',
|
|
80
80
|
overflow: 'hidden',
|
|
81
|
-
minHeight: `${minHeight}px
|
|
82
|
-
maxHeight: `${maxHeight}px
|
|
81
|
+
minHeight: null != minHeight ? `${minHeight}px` : void 0,
|
|
82
|
+
maxHeight: null != maxHeight ? `${maxHeight}px` : void 0,
|
|
83
83
|
position: 'relative'
|
|
84
84
|
}
|
|
85
85
|
}, /*#__PURE__*/ react.createElement(DragHandle, {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type React from 'react';
|
|
1
2
|
/** 函数参数信息 */
|
|
2
3
|
export interface ScriptParameterInfo {
|
|
3
4
|
dataType: string;
|
|
@@ -40,7 +41,8 @@ export interface ScriptRequestInfo {
|
|
|
40
41
|
export interface ScriptMetadata {
|
|
41
42
|
binds: ScriptBindInfo[];
|
|
42
43
|
requests: ScriptRequestInfo[];
|
|
43
|
-
|
|
44
|
+
description?: string;
|
|
45
|
+
mainMethod?: string;
|
|
44
46
|
returnType?: string;
|
|
45
47
|
types: Record<string, ScriptTypeInfo>;
|
|
46
48
|
}
|
|
@@ -51,20 +53,32 @@ export interface ScriptCodeEditorProps {
|
|
|
51
53
|
readonly?: boolean;
|
|
52
54
|
/** 代码变化回调 */
|
|
53
55
|
onChange?: (value: string) => void;
|
|
54
|
-
/** 编译/测试脚本回调(后续接入 API) */
|
|
55
|
-
onCompile?: (code: string) => void;
|
|
56
|
-
/** 主题切换回调 */
|
|
57
|
-
onThemeChange?: (theme: 'dark' | 'light') => void;
|
|
58
56
|
/** 占位符 */
|
|
59
57
|
placeholder?: string;
|
|
60
|
-
/**
|
|
61
|
-
|
|
58
|
+
/** 初始主题(默认 'dark'),编辑器内部管理状态 */
|
|
59
|
+
defaultTheme?: 'dark' | 'light';
|
|
60
|
+
/** 主题切换通知回调(可选) */
|
|
61
|
+
onThemeChange?: (theme: 'dark' | 'light') => void;
|
|
62
62
|
/** 标题(可选) */
|
|
63
63
|
title?: string;
|
|
64
64
|
/** 脚本元数据 — 提供后启用类型面板和自动补全 */
|
|
65
65
|
metadata?: ScriptMetadata;
|
|
66
66
|
/** 类型面板侧边栏默认是否展开(默认:提供 metadata 时为 true) */
|
|
67
67
|
defaultSidebarOpen?: boolean;
|
|
68
|
+
/** 是否显示主题切换按钮(默认 false) */
|
|
69
|
+
enableThemeToggle?: boolean;
|
|
70
|
+
/** 是否显示格式化按钮(默认 false,需配合 onFormat 使用) */
|
|
71
|
+
enableFormat?: boolean;
|
|
72
|
+
/** 是否显示编译验证按钮(默认 false,需配合 onCompile 使用) */
|
|
73
|
+
enableCompile?: boolean;
|
|
74
|
+
/** 是否显示全屏按钮(默认 false) */
|
|
75
|
+
enableFullscreen?: boolean;
|
|
76
|
+
/** 格式化代码回调(enableFormat 时需提供) */
|
|
77
|
+
onFormat?: () => void;
|
|
78
|
+
/** 编译/测试脚本回调(enableCompile 时需提供) */
|
|
79
|
+
onCompile?: (code: string) => void;
|
|
80
|
+
/** 工具栏额外内容(渲染在内置按钮之后,任意 React 节点) */
|
|
81
|
+
toolbarExtra?: React.ReactNode;
|
|
68
82
|
/** 其他选项 */
|
|
69
83
|
options?: {
|
|
70
84
|
/** 字体大小 */
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** 自定义脚本标记 */
|
|
2
|
+
export declare const CUSTOM_SCRIPT = "@CUSTOM_SCRIPT";
|
|
3
|
+
/** 脚本标题标记 */
|
|
4
|
+
export declare const SCRIPT_TITLE = "@SCRIPT_TITLE";
|
|
5
|
+
/** 脚本元数据标记 */
|
|
6
|
+
export declare const SCRIPT_META = "@SCRIPT_META";
|
|
7
|
+
/**
|
|
8
|
+
* 格式化选项接口
|
|
9
|
+
*/
|
|
10
|
+
export interface FormatOptions {
|
|
11
|
+
/** 缩进大小(空格数) */
|
|
12
|
+
indentSize?: number;
|
|
13
|
+
/** 操作符周围添加空格 */
|
|
14
|
+
addSpacesAroundOperators?: boolean;
|
|
15
|
+
/** 格式化注释 */
|
|
16
|
+
formatComments?: boolean;
|
|
17
|
+
/** 最大行长度 */
|
|
18
|
+
maxLineLength?: number;
|
|
19
|
+
/** 保留空行 */
|
|
20
|
+
preserveEmptyLines?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Groovy脚本格式化器
|
|
24
|
+
*/
|
|
25
|
+
export declare class GroovyFormatter {
|
|
26
|
+
/**
|
|
27
|
+
* 格式化Groovy脚本内容
|
|
28
|
+
* @param script Groovy脚本内容
|
|
29
|
+
* @returns 格式化后的脚本
|
|
30
|
+
*/
|
|
31
|
+
static formatScript(script: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* 规范化缩进
|
|
34
|
+
*/
|
|
35
|
+
private static normalizeIndentation;
|
|
36
|
+
/**
|
|
37
|
+
* 判断是否需要增加缩进
|
|
38
|
+
*/
|
|
39
|
+
private static shouldIncreaseIndent;
|
|
40
|
+
/**
|
|
41
|
+
* 判断是否需要减少缩进
|
|
42
|
+
*/
|
|
43
|
+
private static shouldDecreaseIndent;
|
|
44
|
+
/**
|
|
45
|
+
* 增强版格式化:包含更多Groovy特定处理
|
|
46
|
+
*/
|
|
47
|
+
static formatGroovyScript(script: string, options?: FormatOptions): string;
|
|
48
|
+
/**
|
|
49
|
+
* 格式化注释
|
|
50
|
+
*/
|
|
51
|
+
private static formatComments;
|
|
52
|
+
/**
|
|
53
|
+
* 操作符周围添加空格
|
|
54
|
+
*/
|
|
55
|
+
private static addSpacesAroundOperators;
|
|
56
|
+
/**
|
|
57
|
+
* 格式化Groovy特定语法
|
|
58
|
+
*/
|
|
59
|
+
private static formatGroovySpecific;
|
|
60
|
+
/**
|
|
61
|
+
* 压缩脚本(移除所有非必要空白)
|
|
62
|
+
*/
|
|
63
|
+
static minifyScript(script: string): string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Groovy脚本转换器工具类,提供一些通用的脚本处理方法
|
|
67
|
+
*/
|
|
68
|
+
export declare class GroovyScriptConvertorUtil {
|
|
69
|
+
/**
|
|
70
|
+
* 判断脚本是否包含自定义注释标记
|
|
71
|
+
*/
|
|
72
|
+
static isCustomScript(script: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* 格式化脚本内容,去除多余空白等
|
|
75
|
+
*/
|
|
76
|
+
static formatScript(script: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* 将普通脚本转换为包含自定义注释标记的脚本
|
|
79
|
+
*/
|
|
80
|
+
static toCustomScript(script: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* 获取脚本中的标题注释内容
|
|
83
|
+
*/
|
|
84
|
+
static getScriptTitle(script: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* 更新脚本中的标题注释内容,如果不存在则添加
|
|
87
|
+
*/
|
|
88
|
+
static updateScriptTitle(script: string, title: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* 获取脚本中的元数据
|
|
91
|
+
*/
|
|
92
|
+
static getScriptMeta(script: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* 更新脚本中的元数据内容,如果不存在则添加
|
|
95
|
+
*/
|
|
96
|
+
static updateScriptMeta(script: string, meta: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* 清除脚本中的注释
|
|
99
|
+
*/
|
|
100
|
+
static clearComments(script: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* 提取脚本中的return表达式
|
|
103
|
+
*/
|
|
104
|
+
static getReturnScript(script: string): string;
|
|
105
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
const CUSTOM_SCRIPT = '@CUSTOM_SCRIPT';
|
|
2
|
+
const SCRIPT_TITLE = '@SCRIPT_TITLE';
|
|
3
|
+
const SCRIPT_META = '@SCRIPT_META';
|
|
4
|
+
class GroovyFormatter {
|
|
5
|
+
static formatScript(script) {
|
|
6
|
+
if (!script || 0 === script.trim().length) return '';
|
|
7
|
+
let formatted = script;
|
|
8
|
+
formatted = formatted.trim();
|
|
9
|
+
formatted = formatted.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
10
|
+
formatted = formatted.replace(/\n\s*\n/g, '\n\n');
|
|
11
|
+
const lines = formatted.split('\n');
|
|
12
|
+
formatted = lines.map((line)=>line.replace(/\s+$/, '')).join('\n');
|
|
13
|
+
formatted = formatted.replace(/\n+$/, '\n');
|
|
14
|
+
formatted = this.normalizeIndentation(formatted);
|
|
15
|
+
return formatted;
|
|
16
|
+
}
|
|
17
|
+
static normalizeIndentation(script) {
|
|
18
|
+
const lines = script.split('\n');
|
|
19
|
+
const result = [];
|
|
20
|
+
let indentLevel = 0;
|
|
21
|
+
let inMultilineString = false;
|
|
22
|
+
let multilineStringDelimiter = '';
|
|
23
|
+
for(let i = 0; i < lines.length; i++){
|
|
24
|
+
const line = lines[i];
|
|
25
|
+
const trimmedLine = line.trim();
|
|
26
|
+
if (0 === trimmedLine.length) {
|
|
27
|
+
result.push('');
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (inMultilineString) {
|
|
31
|
+
if (trimmedLine.includes(multilineStringDelimiter)) {
|
|
32
|
+
const count = (trimmedLine.match(new RegExp(multilineStringDelimiter, 'g')) || []).length;
|
|
33
|
+
if (count % 2 === 1) {
|
|
34
|
+
inMultilineString = false;
|
|
35
|
+
multilineStringDelimiter = '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} else if (trimmedLine.includes("'''") || trimmedLine.includes('"""')) {
|
|
39
|
+
const delimiter = trimmedLine.includes("'''") ? "'''" : '"""';
|
|
40
|
+
const count = (trimmedLine.match(new RegExp(delimiter, 'g')) || []).length;
|
|
41
|
+
if (count % 2 === 1) {
|
|
42
|
+
inMultilineString = true;
|
|
43
|
+
multilineStringDelimiter = delimiter;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (inMultilineString) result.push(line);
|
|
47
|
+
else {
|
|
48
|
+
const shouldDecreaseIndent = this.shouldDecreaseIndent(trimmedLine);
|
|
49
|
+
if (shouldDecreaseIndent) indentLevel = Math.max(0, indentLevel - 1);
|
|
50
|
+
const indentation = ' '.repeat(indentLevel);
|
|
51
|
+
if (this.shouldIncreaseIndent(trimmedLine)) indentLevel++;
|
|
52
|
+
result.push(indentation + trimmedLine);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result.join('\n');
|
|
56
|
+
}
|
|
57
|
+
static shouldIncreaseIndent(line) {
|
|
58
|
+
const increasePatterns = [
|
|
59
|
+
/.*\{\s*$/,
|
|
60
|
+
/.*\(\s*$/,
|
|
61
|
+
/.*\[\s*$/,
|
|
62
|
+
/^if\s*\(.*\)\s*$/,
|
|
63
|
+
/^for\s*\(.*\)\s*$/,
|
|
64
|
+
/^while\s*\(.*\)\s*$/,
|
|
65
|
+
/^switch\s*\(.*\)\s*$/,
|
|
66
|
+
/^try\s*$/,
|
|
67
|
+
/^catch\s*\(.*\)\s*$/,
|
|
68
|
+
/^finally\s*$/,
|
|
69
|
+
/^else\s*$/,
|
|
70
|
+
/^else\s+if\s*\(.*\)\s*$/,
|
|
71
|
+
/.*->\s*$/,
|
|
72
|
+
/^def\s+\w+\s*=\s*\{/,
|
|
73
|
+
/^class\s+\w+/,
|
|
74
|
+
/^interface\s+\w+/,
|
|
75
|
+
/^enum\s+\w+/,
|
|
76
|
+
/^annotation\s+\w+/,
|
|
77
|
+
/^trait\s+\w+/,
|
|
78
|
+
/^static\s*\{/
|
|
79
|
+
];
|
|
80
|
+
return increasePatterns.some((pattern)=>pattern.test(line));
|
|
81
|
+
}
|
|
82
|
+
static shouldDecreaseIndent(line) {
|
|
83
|
+
const decreasePatterns = [
|
|
84
|
+
/^\}/,
|
|
85
|
+
/^\]/,
|
|
86
|
+
/^\)/,
|
|
87
|
+
/^else\b/,
|
|
88
|
+
/^catch\b/,
|
|
89
|
+
/^finally\b/,
|
|
90
|
+
/^case\b/,
|
|
91
|
+
/^default\s*:/
|
|
92
|
+
];
|
|
93
|
+
return decreasePatterns.some((pattern)=>pattern.test(line));
|
|
94
|
+
}
|
|
95
|
+
static formatGroovyScript(script, options) {
|
|
96
|
+
if (!script || 0 === script.trim().length) return '';
|
|
97
|
+
const opts = {
|
|
98
|
+
indentSize: 4,
|
|
99
|
+
addSpacesAroundOperators: true,
|
|
100
|
+
formatComments: true,
|
|
101
|
+
...options
|
|
102
|
+
};
|
|
103
|
+
let formatted = script;
|
|
104
|
+
formatted = formatted.trim();
|
|
105
|
+
formatted = formatted.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
106
|
+
if (opts.formatComments) formatted = this.formatComments(formatted);
|
|
107
|
+
if (opts.addSpacesAroundOperators) formatted = this.addSpacesAroundOperators(formatted);
|
|
108
|
+
formatted = formatted.replace(/,(?!\s)/g, ', ');
|
|
109
|
+
formatted = formatted.replace(/;(?!\s)/g, '; ');
|
|
110
|
+
formatted = formatted.replace(/\s+/g, ' ');
|
|
111
|
+
const lines = formatted.split(';');
|
|
112
|
+
if (lines.length > 1) formatted = lines.map((line)=>line.trim()).filter((line)=>line.length > 0).join(';\n');
|
|
113
|
+
formatted = this.normalizeIndentation(formatted);
|
|
114
|
+
formatted = this.formatGroovySpecific(formatted);
|
|
115
|
+
return formatted;
|
|
116
|
+
}
|
|
117
|
+
static formatComments(script) {
|
|
118
|
+
let formatted = script.replace(/\/\/\s*/g, '// ');
|
|
119
|
+
formatted = formatted.replace(/\/\*(\s*)\*\//g, '/**$1*/');
|
|
120
|
+
return formatted;
|
|
121
|
+
}
|
|
122
|
+
static addSpacesAroundOperators(script) {
|
|
123
|
+
const operators = [
|
|
124
|
+
'=',
|
|
125
|
+
'==',
|
|
126
|
+
'!=',
|
|
127
|
+
'===',
|
|
128
|
+
'!==',
|
|
129
|
+
'>',
|
|
130
|
+
'<',
|
|
131
|
+
'>=',
|
|
132
|
+
'<=',
|
|
133
|
+
'+',
|
|
134
|
+
'-',
|
|
135
|
+
'*',
|
|
136
|
+
'/',
|
|
137
|
+
'%',
|
|
138
|
+
'&&',
|
|
139
|
+
'||',
|
|
140
|
+
'&',
|
|
141
|
+
'|',
|
|
142
|
+
'^',
|
|
143
|
+
'<<',
|
|
144
|
+
'>>',
|
|
145
|
+
'>>>',
|
|
146
|
+
'+=',
|
|
147
|
+
'-=',
|
|
148
|
+
'*=',
|
|
149
|
+
'/=',
|
|
150
|
+
'%=',
|
|
151
|
+
'&=',
|
|
152
|
+
'|=',
|
|
153
|
+
'^=',
|
|
154
|
+
'<<=',
|
|
155
|
+
'>>=',
|
|
156
|
+
'>>>='
|
|
157
|
+
];
|
|
158
|
+
let formatted = script;
|
|
159
|
+
operators.forEach((op)=>{
|
|
160
|
+
const regex = new RegExp(`(?<![\\'"\\\`])\\\\${op}(?![\\'"\\\`])`, 'g');
|
|
161
|
+
formatted = formatted.replace(regex, ` ${op} `);
|
|
162
|
+
});
|
|
163
|
+
formatted = formatted.replace(/\s+/g, ' ');
|
|
164
|
+
return formatted;
|
|
165
|
+
}
|
|
166
|
+
static formatGroovySpecific(script) {
|
|
167
|
+
let formatted = script;
|
|
168
|
+
formatted = formatted.replace(/\$\{(\s*)(\w+)(\s*)\}/g, '${$2}');
|
|
169
|
+
formatted = formatted.replace(/\{(\s*)(\w+)(\s*)->/g, '{ $2 ->');
|
|
170
|
+
formatted = formatted.replace(/\[(\s*)(\w+)(\s*):/g, '[$2:');
|
|
171
|
+
formatted = formatted.replace(/@(\w+)\((\s*)(\w+)(\s*)=/g, '@$1($3=');
|
|
172
|
+
formatted = formatted.replace(/(\w+)(\s*)\?\.(\s*)(\w+)/g, '$1?.$4');
|
|
173
|
+
formatted = formatted.replace(/(\w+)(\s*)\*\.(\s*)(\w+)/g, '$1*.$4');
|
|
174
|
+
return formatted;
|
|
175
|
+
}
|
|
176
|
+
static minifyScript(script) {
|
|
177
|
+
if (!script || 0 === script.trim().length) return '';
|
|
178
|
+
let minified = script;
|
|
179
|
+
minified = minified.replace(/\/\/.*$/gm, '');
|
|
180
|
+
minified = minified.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
181
|
+
minified = minified.replace(/\s+/g, ' ');
|
|
182
|
+
minified = minified.replace(/\s*([{}();,=+\-*/%&|<>!])\s*/g, '$1');
|
|
183
|
+
minified = minified.replace(/\n\s*\n/g, '\n');
|
|
184
|
+
return minified.trim();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
class GroovyScriptConvertorUtil {
|
|
188
|
+
static isCustomScript(script) {
|
|
189
|
+
if (!script) return false;
|
|
190
|
+
return script.includes(CUSTOM_SCRIPT);
|
|
191
|
+
}
|
|
192
|
+
static formatScript(script) {
|
|
193
|
+
if (!script) return '';
|
|
194
|
+
return GroovyFormatter.formatScript(script);
|
|
195
|
+
}
|
|
196
|
+
static toCustomScript(script) {
|
|
197
|
+
if (GroovyScriptConvertorUtil.isCustomScript(script)) return GroovyFormatter.formatScript(script);
|
|
198
|
+
return GroovyFormatter.formatScript(`// ${CUSTOM_SCRIPT}\n${script}`);
|
|
199
|
+
}
|
|
200
|
+
static getScriptTitle(script) {
|
|
201
|
+
if (!script) return '';
|
|
202
|
+
const titleMatch = script.match(new RegExp(`//\\s*${SCRIPT_TITLE}\\s*(.+)`));
|
|
203
|
+
if (titleMatch) return titleMatch[1].trim();
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
static updateScriptTitle(script, title) {
|
|
207
|
+
const titleComment = `// ${SCRIPT_TITLE} ${title}`;
|
|
208
|
+
if (GroovyScriptConvertorUtil.getScriptTitle(script)) return script.replace(new RegExp(`//\\s*${SCRIPT_TITLE}\\s*.+`), titleComment);
|
|
209
|
+
return `${titleComment}\n${script}`;
|
|
210
|
+
}
|
|
211
|
+
static getScriptMeta(script) {
|
|
212
|
+
if (!script) return '';
|
|
213
|
+
const titleMatch = script.match(new RegExp(`//\\s*${SCRIPT_META}\\s*(.+)`));
|
|
214
|
+
if (titleMatch) return titleMatch[1].trim();
|
|
215
|
+
return '';
|
|
216
|
+
}
|
|
217
|
+
static updateScriptMeta(script, meta) {
|
|
218
|
+
const metaComment = `// ${SCRIPT_META} ${meta}`;
|
|
219
|
+
if (GroovyScriptConvertorUtil.getScriptMeta(script)) return script.replace(new RegExp(`//\\s*${SCRIPT_META}\\s*.+`), metaComment);
|
|
220
|
+
return `${metaComment}\n${script}`;
|
|
221
|
+
}
|
|
222
|
+
static clearComments(script) {
|
|
223
|
+
if (!script) return '';
|
|
224
|
+
return script.replace(/\/\/.*$/gm, '').trim();
|
|
225
|
+
}
|
|
226
|
+
static getReturnScript(script) {
|
|
227
|
+
try {
|
|
228
|
+
let result = GroovyScriptConvertorUtil.clearComments(script);
|
|
229
|
+
const funcMatch = result.match(/def\s+run\s*\([^)]*\)\s*\{([\s\S]*)\}/);
|
|
230
|
+
if (funcMatch) result = funcMatch[1];
|
|
231
|
+
const returnMatch = result.match(/return\s+(.+?);?\s*$/m);
|
|
232
|
+
result = returnMatch ? returnMatch[1].trim() : result.trim();
|
|
233
|
+
return result;
|
|
234
|
+
} catch {
|
|
235
|
+
return '';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export { CUSTOM_SCRIPT, GroovyFormatter, GroovyScriptConvertorUtil, SCRIPT_META, SCRIPT_TITLE };
|