@tntd/monaco-editor 1.0.1 → 1.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.
@@ -0,0 +1,389 @@
1
+ // - Monaco编辑器初始化 :封装了Monaco编辑器的初始化逻辑 生命周期
2
+ // - 主题定制 :支持自定义tntd-vs-dark主题
3
+ // - 语言支持 :支持多种编程语言的语法高亮和智能提示
4
+ import loader from '@monaco-editor/loader';
5
+ import { getLang } from '../I18N';
6
+ import { loadMyLanguage } from '../theme/defineScript';
7
+ import { loadMyTheme } from '../theme/defineTheme';
8
+
9
+ export function escapeRegExp(string) {
10
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
11
+ }
12
+
13
+ // 设置同盾tntd-vs-dark主题颜色
14
+ const defaultThemeColor = ({ themeColor }) => ({
15
+ 'editor.background': '#17233D',
16
+ 'editor.foreground': '#BABDC5',
17
+ 'editorGutter.background': '#212c45',
18
+ 'editorLineNumber.foreground': '#BABDC5',
19
+ 'editorCursor.foreground': '#ff9800', // 设置光标颜色
20
+ 'editor.lineHighlightBackground': '#012d57', // 当前行高亮背景色
21
+ 'editor.lineHighlightBorder': '#012d57', // 当前行高亮边框颜色
22
+ 'editor.selectionBackground': '#264f78', // 选中文本背景色
23
+ 'editor.selectionHighlightBackground': '#5f5f5f', // 选中高亮背景色
24
+ focusBorder: '#212c45',
25
+ ...(themeColor || {})
26
+ });
27
+
28
+ // 默认编辑器配置
29
+ const defaultOptions = {
30
+ selectOnLineNumbers: true,
31
+ roundedSelection: false,
32
+ autoIndent: true,
33
+ fontSize: 14,
34
+ lineHeight: 24,
35
+ insertSpaces: true,
36
+ tabSize: 2,
37
+ lineDecorationsWidth: 7,
38
+ glyphMargin: true,
39
+ renderLineHighlight: 'line', //all
40
+ lineNumbers: (lineNumber) => String(lineNumber).padStart(2, '0'),
41
+ wordWrap: 'on', // 或 "on"
42
+ // 其他可选配置(如显示换行后的行号)
43
+ wrapLineNumbers: true, //
44
+ lineNumbersMinChars: 2,
45
+ contextmenu: false, // 禁用右键菜单
46
+ folding: true,
47
+ foldingStrategy: 'auto',
48
+ // wrappingIndent: 'indent',
49
+ formatOnPaste: true,
50
+ formatOnType: false,
51
+ dragAndDrop: true,
52
+ cursorStyle: 'line-thin',
53
+ cursorBlinking: 'blink',
54
+ suggestOnTriggerCharacters: true,
55
+ quickSuggestions: {
56
+ other: true,
57
+ comments: false,
58
+ strings: true
59
+ },
60
+ minimap: { enabled: false },
61
+ autoClosingQuotes: true,
62
+ autoClosingBrackets: true,
63
+ autoClosingOvertype: 'always',
64
+ autoClosingDelete: 'always',
65
+ automaticLayout: true,
66
+ comments: {
67
+ allowEmptyComment: true,
68
+ ignoreEmptyComments: true,
69
+ insertSpaces: true,
70
+ blockComment: ['/*', '*/'],
71
+ lineComment: ['//', '//']
72
+ },
73
+ scrollbar: {
74
+ vertical: 'hidden',
75
+ horizontal: 'auto',
76
+ verticalScrollbarSize: 8,
77
+ horizontalScrollbarSize: 8,
78
+ alwaysConsumeMouseWheel: false
79
+ },
80
+ scrollBeyondLastLine: false,
81
+ overviewRulerBorder: false,
82
+ renderValidationDecorations: 'off',
83
+ overviewRulerLanes: 0
84
+ };
85
+
86
+ export class BaseEditor {
87
+ constructor(container, options = {}) {
88
+ this.container = container;
89
+ this.options = options;
90
+ this.monaco = null;
91
+ this.editor = null;
92
+ this.disposables = new Set();
93
+ this.plugins = new Map();
94
+ this.modeField = options.modeField || null; // 从选项中获取初始 modeField
95
+ this.defaultJsLang = null;
96
+
97
+ //加载配置
98
+ loader.config({
99
+ paths: { vs: options.path || process.env.publicPath + '/vendor/vs' },
100
+ ...(getLang() === 'cn' ? { 'vs/nls': { availableLanguages: { '*': 'zh-cn' } } } : {})
101
+ });
102
+ this.monacoLoader = loader.init();
103
+ }
104
+
105
+ // 自定义同盾的vs-dark主题
106
+ async customTntdTheme() {
107
+ if (this.hasCustomTntdTheme) {
108
+ return;
109
+ }
110
+
111
+ // 如果有关键字 针对关键字做高亮处理
112
+ const { keywords = [], language, themeColor } = this.options || {};
113
+ if (keywords?.length) {
114
+ const { tokenizer, ...lang } = this.defaultJsLang || {};
115
+ const { root, ...rest } = tokenizer || {};
116
+
117
+ let keyPattern;
118
+ if (keywords?.length) {
119
+ const escapedKeyList = keywords.map((name) => escapeRegExp(name));
120
+ keyPattern = escapedKeyList.map((name) => `${name}`).join('|');
121
+ }
122
+ this.monaco.languages.setMonarchTokensProvider(language, {
123
+ tokenizer: {
124
+ root: [[new RegExp(`\\b(${keyPattern})\\b`, 'gi'), { token: 'keyword-field' }], ...(root || [])],
125
+ ...(rest || {})
126
+ },
127
+ ...lang
128
+ });
129
+ }
130
+
131
+ // 自定义同盾的vs-dark主题
132
+ this.monaco.editor.defineTheme('tntd-vs-dark', {
133
+ base: 'vs-dark',
134
+ inherit: true,
135
+ rules: [
136
+ {
137
+ token: 'keyword-field',
138
+ foreground: '#569cd6'
139
+ }
140
+ ],
141
+ colors: defaultThemeColor({ themeColor })
142
+ });
143
+ this.hasCustomTntdTheme = true;
144
+ }
145
+
146
+ // 初始化编辑器
147
+ async init() {
148
+ try {
149
+ // 初始化 Monaco
150
+ this.monaco = await this.monacoLoader.then((res) => {
151
+ return res;
152
+ });
153
+
154
+ // 执行 willMount 生命周期
155
+ let finalOptions = this.options;
156
+
157
+ if (this.options.editorWillMount && typeof this.options.editorWillMount === 'function') {
158
+ const willMountOptions = await this.options.editorWillMount(this.monaco);
159
+ finalOptions = { ...defaultOptions, ...this.options, ...willMountOptions };
160
+ }
161
+
162
+ if (finalOptions.readOnly) {
163
+ finalOptions.cursorStyle = 'hidden';
164
+ }
165
+
166
+ if (finalOptions?.isFormula) {
167
+ finalOptions.suggestOnTriggerCharacters = false;
168
+ finalOptions.quickSuggestions = {
169
+ other: false,
170
+ comments: false,
171
+ strings: false
172
+ };
173
+ }
174
+
175
+ const languages = this.monaco.languages.getLanguages();
176
+ const { language, isFormula, themeColor } = finalOptions || {};
177
+ let defaultJsLang = languages.find((item) => item.id === language);
178
+ if (!defaultJsLang || isFormula) {
179
+ defaultJsLang = await languages.find((item) => item.id === 'javascript');
180
+ }
181
+ if (defaultJsLang) {
182
+ defaultJsLang = await defaultJsLang.loader();
183
+ this.defaultJsLang = defaultJsLang.language;
184
+ }
185
+
186
+ // 自定义同盾的vs-dark主题
187
+ await this.customTntdTheme();
188
+ // 配置编辑器
189
+ this.monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
190
+
191
+ // 如果是公式 modeField,加载语言以及主题配置
192
+ if (isFormula) {
193
+ if (this.modeField) {
194
+ await loadMyLanguage({
195
+ monaco: this.monaco,
196
+ modeField: this.modeField,
197
+ defaultJsLang,
198
+ languageId: this.options.language
199
+ });
200
+ }
201
+ await loadMyTheme({ monaco: this.monaco, defaultThemeColor: defaultThemeColor({ themeColor }) });
202
+ }
203
+
204
+ // 创建编辑器实例
205
+ if (this.options.isDiff) {
206
+ this.editor = this.monaco.editor.createDiffEditor(this.container, finalOptions);
207
+ this.editor.setModel({
208
+ original: this.monaco.editor.createModel(this.options.original, this.options.originalLanguage || this.options.language),
209
+ modified: this.monaco.editor.createModel(this.options.modified, this.options.modifiedLanguage || this.options.language)
210
+ });
211
+ } else {
212
+ this.editor = this.monaco.editor.create(this.container, finalOptions);
213
+ }
214
+ this.codeEditor = this.editor;
215
+
216
+ // 执行 didMount 生命周期
217
+ if (finalOptions.editorDidMount) {
218
+ await finalOptions.editorDidMount({
219
+ monaco: this.monaco,
220
+ editor: this.editor
221
+ });
222
+ }
223
+
224
+ return {
225
+ monaco: this.monaco,
226
+ editor: this.editor
227
+ };
228
+ } catch (error) {
229
+ console.log('Monaco Editor initialization failed:');
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ // 更新语言配置
235
+ async updateLanguage(modeField) {
236
+ if (!this.monaco || !modeField) return;
237
+
238
+ this.modeField = modeField;
239
+
240
+ // 加载语言和主题
241
+ if (this.options.isFormula) {
242
+ loadMyLanguage({
243
+ monaco: this.monaco,
244
+ modeField: this.modeField,
245
+ defaultJsLang: this.defaultJsLang,
246
+ languageId: this.options.language
247
+ });
248
+ // loadMyTheme({ monaco: this.monaco, defaultThemeColor });
249
+ }
250
+ }
251
+
252
+ // 插件系统
253
+ use(plugin) {
254
+ if (this.plugins.has(plugin.name)) {
255
+ this.plugins.get(plugin.name).dispose();
256
+ }
257
+ this.plugins.set(plugin.name, plugin);
258
+ plugin.apply(this);
259
+ return this;
260
+ }
261
+
262
+ // 事件系统
263
+ on(event, callback) {
264
+ let disposable;
265
+ switch (event) {
266
+ case 'change':
267
+ disposable = this.editor?.onDidChangeModelContent?.(() => {
268
+ callback(this.getValue(), this.editor, this.monaco);
269
+ });
270
+ break;
271
+ case 'cursorChange': {
272
+ disposable = this.editor?.onDidChangeCursorPosition?.((event) => {
273
+ callback(event, this.editor, this.monaco);
274
+ });
275
+ break;
276
+ }
277
+
278
+ default:
279
+ return null;
280
+ }
281
+ if (disposable) {
282
+ this.disposables.add(disposable);
283
+ }
284
+ return disposable;
285
+ }
286
+
287
+ clearAllLanguages(monaco) {
288
+ // 获取所有已注册的语言
289
+ const languages = monaco.languages.getLanguages();
290
+ // 为每个语言创建一个空配置
291
+ languages.forEach((lang) => {
292
+ if (lang.id === this.options.language && this.options.language?.startsWith('formulaLang_')) {
293
+ // 重置词法规则
294
+ monaco.languages.setMonarchTokensProvider(lang.id, {
295
+ tokenizer: {
296
+ root: [] // 空词法规则
297
+ }
298
+ });
299
+
300
+ // 移除自动补全提供者
301
+ const disposable = monaco.languages.registerCompletionItemProvider(lang.id, {
302
+ provideCompletionItems: () => ({ suggestions: [] })
303
+ });
304
+
305
+ // 立即释放
306
+ disposable.dispose();
307
+ }
308
+ });
309
+ }
310
+
311
+ // 销毁编辑器
312
+ dispose() {
313
+ // 执行 willUnmount 生命周期
314
+ if (this.options.editorWillUnmount) {
315
+ this.options.editorWillUnmount(this.editor);
316
+ }
317
+
318
+ // 清理插件
319
+ this.plugins.forEach((plugin) => plugin?.dispose?.());
320
+ this.plugins.clear();
321
+
322
+ this.clearAllLanguages(this.monaco);
323
+
324
+ // 清理事件监听
325
+ this.disposables.forEach((disposable) => disposable?.dispose?.());
326
+ this.disposables.clear();
327
+
328
+ // 销毁编辑器
329
+ this.editor?.dispose?.();
330
+ this.editor = null;
331
+ }
332
+
333
+ // 基础编辑器方法
334
+ getValue() {
335
+ return this.editor?.getModel()?.getValue() || '';
336
+ }
337
+
338
+ setValue(value) {
339
+ this.editor?.getModel()?.setValue(value || '');
340
+ }
341
+
342
+ getPosition() {
343
+ return this.editor?.getPosition();
344
+ }
345
+
346
+ setPosition(position) {
347
+ this.editor?.setPosition(position);
348
+ }
349
+
350
+ getModel() {
351
+ return this.editor?.getModel();
352
+ }
353
+
354
+ layout() {
355
+ this.editor?.layout();
356
+ }
357
+
358
+ focus() {
359
+ this.editor?.focus();
360
+ }
361
+
362
+ updateOptions(options) {
363
+ this.editor?.updateOptions(options);
364
+ }
365
+
366
+ // 设置编辑器语言
367
+ setModelLanguage(model, language) {
368
+ if (!this.monaco || !model) return;
369
+ this.monaco.editor.setModelLanguage(model, language);
370
+ }
371
+
372
+ getCursor = () => {
373
+ const position = this.editor.getPosition();
374
+ return position;
375
+ };
376
+
377
+ insertText = (content) => {
378
+ const selections = this.editor.getSelections();
379
+ if (selections) {
380
+ const edits = selections.map((selection) => ({
381
+ range: selection,
382
+ text: content,
383
+ forceMoveMarkers: true
384
+ }));
385
+ this.editor.executeEdits('edit-selection', edits);
386
+ this.focus();
387
+ }
388
+ };
389
+ }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import './index.less';
2
+ import BaseMonacoEditor from './BaseMonacoEditor';
3
+ import FormulaEditor from './FormulaEditor';
4
+ import DiffEditor from './DiffEditor';
5
+ // import BaseMonacoEditor, { FormulaEditor, DiffEditor } from '@tntd/monaco-editor';
6
+ export default BaseMonacoEditor;
7
+ export { FormulaEditor, DiffEditor };
package/src/index.less ADDED
@@ -0,0 +1,11 @@
1
+ .tnt-react-monaco-editor-container {
2
+ background-color: #17233d;
3
+ .line-numbers{
4
+ word-wrap: normal !important;
5
+ font-family: Helvetica, Arial, sans-serif, Microsoft YaHei, Menlo, Monaco, "Courier New", monospace ;
6
+ }
7
+ }
8
+
9
+ .tnt-codemirror{
10
+ border:none !important;
11
+ }
@@ -0,0 +1,148 @@
1
+ import { useEffect, useState, useMemo } from 'react';
2
+ import { TntdCascader, Tooltip } from 'tntd';
3
+ import TdTag from '@tntd/cascader-tag';
4
+ import './Cascader.less';
5
+
6
+ export default (props) => {
7
+ const {
8
+ notSupportArrayData,
9
+ selectChange,
10
+ style = {},
11
+ tipShow,
12
+ dropList,
13
+ fieldInfo,
14
+ isCascader = true,
15
+ changeOnSelect = true,
16
+ showSourceName = true,
17
+ placement,
18
+ fieldNames,
19
+ dropdownClassName
20
+ } = props;
21
+ const [popupVisible, setPopupVisible] = useState(true); // 初始展开状态为true
22
+ const [options, setOptions] = useState([]);
23
+ const [differentKeys, setDifferentKeys] = useState(0); // 表示不只是字段。存在字段/指标等场景
24
+
25
+ useEffect(() => {
26
+ if (!tipShow) {
27
+ setPopupVisible(tipShow);
28
+ }
29
+ }, [tipShow]);
30
+
31
+ // 判断是否展示级联
32
+ const hasCascaderKey = useMemo(() => {
33
+ return isCascader && dropList?.[0]?.hasOwnProperty('sourceKey') && dropList?.[0]?.sourceKey;
34
+ }, [isCascader, dropList]);
35
+
36
+ useEffect(() => {
37
+ const [_filterOptions, optionType, _filterMapOption] = [[], {}, {}];
38
+ if (dropList?.length) {
39
+ let [differentKeys, differentSubKeys] = [0, 0];
40
+ let sourceSubMap = {};
41
+ // 如果列表数据有sourceKey 或者 指定isCascader 的则自动升级成
42
+ if (!fieldInfo && hasCascaderKey) {
43
+ dropList?.forEach((obj) => {
44
+ const { sourceKey, sourceName, sourceKey_sub, sourceName_sub, name, isNotFirst } = obj;
45
+ if (!isNotFirst) {
46
+ _filterMapOption[obj.value] = obj;
47
+ if (sourceKey && !optionType[sourceKey]) {
48
+ differentKeys++;
49
+ optionType[sourceKey] = { title: sourceName, value: sourceKey, name: sourceName, children: [] };
50
+ _filterOptions.push(optionType[sourceKey]);
51
+ }
52
+
53
+ if (sourceKey_sub) {
54
+ const subData = optionType[sourceKey].children.find((data) => data.value === sourceKey_sub);
55
+ if (!sourceSubMap?.[sourceKey_sub]) {
56
+ sourceSubMap[sourceKey_sub] = true;
57
+ differentSubKeys++;
58
+ }
59
+ if (subData) {
60
+ subData.children.push({ title: sourceName_sub, ...obj });
61
+ } else {
62
+ optionType[sourceKey].children.push({
63
+ title: sourceName_sub,
64
+ name: sourceName_sub,
65
+ value: sourceKey_sub,
66
+ children: [{ title: name, ...obj }]
67
+ });
68
+ }
69
+ }
70
+ }
71
+ });
72
+ if (Math.max(differentKeys, differentSubKeys) > 1) {
73
+ setOptions(_filterOptions);
74
+ } else {
75
+ setOptions(dropList);
76
+ }
77
+ setDifferentKeys(Math.max(differentKeys, differentSubKeys));
78
+ } else {
79
+ setOptions(dropList);
80
+ }
81
+ } else {
82
+ setOptions([]);
83
+ }
84
+ }, [dropList, hasCascaderKey]);
85
+
86
+ const renderItem = (data, i) => {
87
+ const nameKey = fieldNames?.label || 'name';
88
+ const dName = data?.[nameKey]?.replace(/【.*?】/g, '');
89
+ return (
90
+ <Tooltip placement="topLeft" title={dName}>
91
+ <span>
92
+ {!!(data?.type || data?.datatype || data?.dataType) && (
93
+ <TdTag
94
+ showSourceName={differentKeys < 2 && showSourceName && i === 0 && !!data?.sourceName}
95
+ data={{ ...data, showSub: differentKeys < 2 && showSourceName }}
96
+ />
97
+ )}
98
+ <span title={dName}>{dName}</span>
99
+ </span>
100
+ </Tooltip>
101
+ );
102
+ };
103
+
104
+ return (
105
+ <div className="monaco-scroll-container" style={style}>
106
+ <TntdCascader
107
+ placement={placement || 'bottomLeft'}
108
+ dropdownClassName={`monaco-formula-drop-cascader ${dropdownClassName || ''} ${differentKeys < 2 ? '' : 'more-levels'}`}
109
+ expandTrigger={changeOnSelect && hasCascaderKey ? 'hover' : 'click'}
110
+ fieldNames={fieldNames}
111
+ // searchValue={content || undefined}
112
+ options={options}
113
+ open={popupVisible}
114
+ changeOnSelect={changeOnSelect}
115
+ renderItem={renderItem}
116
+ onDropdownVisibleChange={() => setPopupVisible(true)}
117
+ showCheckedStrategy={TntdCascader.SHOW_PARENT}
118
+ onChange={(val, selectedOptions) => {
119
+ const [nameKey] = [fieldNames?.label || 'name'];
120
+ // 不需要回显示sourceKey,sourceKey_sub
121
+ if (!fieldInfo && hasCascaderKey && differentKeys > 1) {
122
+ selectedOptions = selectedOptions?.slice?.(2);
123
+ }
124
+ if (selectedOptions?.length) {
125
+ const name = [];
126
+ selectedOptions.forEach((item, i) => {
127
+ let quote = '';
128
+
129
+ if (!notSupportArrayData && item && (item.type === 'ARRAY' || item.array)) {
130
+ quote = '[]';
131
+ }
132
+ if (i > 0) {
133
+ name.push('@' + item[nameKey] + quote);
134
+ } else {
135
+ name.push(item[nameKey] + quote);
136
+ }
137
+ });
138
+ selectChange({
139
+ ...(selectedOptions?.[0] || {}),
140
+ ...(selectedOptions?.slice(-1)?.[0] || {}),
141
+ name: name.join('.')
142
+ });
143
+ }
144
+ }}
145
+ />
146
+ </div>
147
+ );
148
+ };
@@ -0,0 +1,59 @@
1
+
2
+ .monaco-scroll-container{
3
+ position: fixed;
4
+ left: 0;
5
+ top: 0;
6
+ height: 0 !important;
7
+ width: 0 !important;
8
+ // z-index: 1000;
9
+ >.tntd-cascader{
10
+ height: 0 ;
11
+ width:0;
12
+ opacity: 0;
13
+ *{
14
+ height: 0 !important;
15
+ width: 0 !important;
16
+ opacity: 0;
17
+ }
18
+ }
19
+ }
20
+
21
+ .monaco-formula-drop-cascader{
22
+ &.tntd-select-dropdown{
23
+ z-index: 1050 !important;
24
+ }
25
+ &.more-levels{
26
+ .tntd-cascader-menu{
27
+ &:nth-child(1),&:nth-child(2){
28
+ width: 120px;
29
+ }
30
+ .rc-virtual-list{
31
+ width: auto;
32
+ }
33
+ }
34
+ }
35
+ &.tntd-cascader-dropdown .tntd-cascader-menu .rc-virtual-list{
36
+ width: auto ;
37
+ }
38
+ &:not(.more-levels){
39
+ .tntd-cascader-menus{
40
+ .tntd-cascader-menu{
41
+ &:only-child{
42
+ width: 280px !important;
43
+ }
44
+ }
45
+ }
46
+ }
47
+ .tntd-cascader-menus{
48
+ width:auto;
49
+ .tntd-cascader-menu{
50
+ width: 180px !important;
51
+ .td-tag{
52
+ overflow: hidden;
53
+ text-overflow: ellipsis;
54
+ white-space: nowrap;
55
+ display: contents;
56
+ }
57
+ }
58
+ }
59
+ }