@nethru/ui 2.1.37 → 2.1.38
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/base/editor/Editor.js +166 -131
- package/package.json +1 -1
package/base/editor/Editor.js
CHANGED
|
@@ -42,7 +42,77 @@ export default function Editor({
|
|
|
42
42
|
const [view, setView] = useState();
|
|
43
43
|
const [content, setContent] = useState('');
|
|
44
44
|
const [focused, setFocused] = useState(false);
|
|
45
|
-
const theme = useMemo(() =>
|
|
45
|
+
const theme = useMemo(() => makeTheme(focused, error), [focused, error]);
|
|
46
|
+
const highlight = useMemo(() => makeHighlight(), []);
|
|
47
|
+
// eslint-disable-next-line
|
|
48
|
+
const styles = useMemo(() => makeStyle(restrictWidth, ref.current), [restrictWidth, ref.current]);
|
|
49
|
+
const eslintLinter = useMemo(() => makeEslinter(format, eslintRules, onEslintUpdate, customVariables), [format, eslintRules, onEslintUpdate, customVariables]);
|
|
50
|
+
const languageExtension = useMemo(() => getLanguageExtension(format), [format]);
|
|
51
|
+
const keymapExtension = useMemo(() => getKeymapExtension(keymap), [keymap]);
|
|
52
|
+
const extensionList = useMemo(() => makeExtensions(languageExtension, eslintLinter, highlight.extension, keymapExtension, extensions, onScroll), [languageExtension, eslintLinter, highlight.extension, keymapExtension, extensions, onScroll]);
|
|
53
|
+
const handleUpdate = useCallback(viewUpdate => {
|
|
54
|
+
setFocused(viewUpdate.view.hasFocus);
|
|
55
|
+
if (onUpdate) onUpdate(viewUpdate);
|
|
56
|
+
}, [onUpdate]);
|
|
57
|
+
const highlightKeyword = useCallback(() => {
|
|
58
|
+
if (!view || !keyword) return;
|
|
59
|
+
const {
|
|
60
|
+
effect,
|
|
61
|
+
decoration
|
|
62
|
+
} = highlight;
|
|
63
|
+
const cursor = isRegExp ? new RegExpCursor(view.state.doc, keyword) : new SearchCursor(view.state.doc, keyword, undefined, undefined, s => s.toLowerCase());
|
|
64
|
+
while (true) {
|
|
65
|
+
cursor.next();
|
|
66
|
+
if (cursor.done) break;
|
|
67
|
+
view.dispatch({
|
|
68
|
+
effects: effect.of([decoration.range(cursor.value.from, cursor.value.to)])
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}, [view, highlight, keyword, isRegExp]);
|
|
72
|
+
const scrollToBottom = useCallback(() => {
|
|
73
|
+
if (!view || !autoScrollToBottom) return;
|
|
74
|
+
view.dispatch({
|
|
75
|
+
effects: EditorView.scrollIntoView(view.state.doc.length)
|
|
76
|
+
});
|
|
77
|
+
}, [view, autoScrollToBottom]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setContent(value);
|
|
80
|
+
}, [value]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
highlightKeyword();
|
|
83
|
+
scrollToBottom();
|
|
84
|
+
}, [content, view, highlightKeyword, scrollToBottom]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (focused && onFocus) onFocus();
|
|
87
|
+
if (!focused && onBlur) onBlur();
|
|
88
|
+
}, [focused, onFocus, onBlur]);
|
|
89
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
90
|
+
ref: ref,
|
|
91
|
+
children: [/*#__PURE__*/_jsx(CodeMirror, {
|
|
92
|
+
value: content,
|
|
93
|
+
editable: !readOnly,
|
|
94
|
+
extensions: extensionList,
|
|
95
|
+
basicSetup: {
|
|
96
|
+
...basicSetup,
|
|
97
|
+
highlightActiveLineGutter: false,
|
|
98
|
+
highlightActiveLine: !readOnly
|
|
99
|
+
},
|
|
100
|
+
onCreateEditor: (view, state) => setView(view),
|
|
101
|
+
onUpdate: handleUpdate,
|
|
102
|
+
theme: theme,
|
|
103
|
+
style: {
|
|
104
|
+
...style,
|
|
105
|
+
...styles
|
|
106
|
+
},
|
|
107
|
+
...props
|
|
108
|
+
}), helperText && /*#__PURE__*/_jsx(FormHelperText, {
|
|
109
|
+
error: error,
|
|
110
|
+
children: helperText
|
|
111
|
+
})]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function makeTheme(focused, error) {
|
|
115
|
+
return EditorView.theme({
|
|
46
116
|
'&': {
|
|
47
117
|
fontSize: '13px',
|
|
48
118
|
outline: 'none !important'
|
|
@@ -95,59 +165,48 @@ export default function Editor({
|
|
|
95
165
|
}
|
|
96
166
|
}, {
|
|
97
167
|
dark: false
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
const extension = StateField.define({
|
|
113
|
-
create() {
|
|
114
|
-
return Decoration.none;
|
|
115
|
-
},
|
|
116
|
-
update(value, transaction) {
|
|
117
|
-
value = value.map(transaction.changes);
|
|
118
|
-
for (let effect of transaction.effects) {
|
|
119
|
-
if (effect.is(highlightEffect)) {
|
|
120
|
-
value = value.update({
|
|
121
|
-
add: effect.value,
|
|
122
|
-
sort: true
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return value;
|
|
127
|
-
},
|
|
128
|
-
provide: field => EditorView.decorations.from(field)
|
|
129
|
-
});
|
|
130
|
-
return {
|
|
131
|
-
effect: highlightEffect,
|
|
132
|
-
decoration: decoration,
|
|
133
|
-
extension: extension
|
|
134
|
-
};
|
|
135
|
-
}, []);
|
|
136
|
-
const languageExtension = useMemo(() => {
|
|
137
|
-
switch (format) {
|
|
138
|
-
case 'json':
|
|
139
|
-
return json();
|
|
140
|
-
case 'nscript':
|
|
141
|
-
return undefined;
|
|
142
|
-
default:
|
|
143
|
-
return javascript({
|
|
144
|
-
jsx: true
|
|
145
|
-
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function makeStyle(restrictWidth, instance) {
|
|
171
|
+
return {
|
|
172
|
+
maxWidth: restrictWidth && instance ? `${instance.offsetWidth}px` : 'unset'
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function makeHighlight() {
|
|
176
|
+
const highlightEffect = StateEffect.define();
|
|
177
|
+
const decoration = Decoration.mark({
|
|
178
|
+
attributes: {
|
|
179
|
+
style: `background-color: ${yellow[100]}`
|
|
146
180
|
}
|
|
147
|
-
}
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
181
|
+
});
|
|
182
|
+
const extension = StateField.define({
|
|
183
|
+
create() {
|
|
184
|
+
return Decoration.none;
|
|
185
|
+
},
|
|
186
|
+
update(value, transaction) {
|
|
187
|
+
value = value.map(transaction.changes);
|
|
188
|
+
for (let effect of transaction.effects) {
|
|
189
|
+
if (effect.is(highlightEffect)) {
|
|
190
|
+
value = value.update({
|
|
191
|
+
add: effect.value,
|
|
192
|
+
sort: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
},
|
|
198
|
+
provide: field => EditorView.decorations.from(field)
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
effect: highlightEffect,
|
|
202
|
+
decoration: decoration,
|
|
203
|
+
extension: extension
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function makeEslinter(format, eslintRules, onEslintUpdate, customVariables) {
|
|
207
|
+
const regex = /\{\{(\S+)\}\}/g;
|
|
208
|
+
if (format === 'javascript') {
|
|
209
|
+
return linter(view => {
|
|
151
210
|
const text = view.state.doc.toString();
|
|
152
211
|
const linter = new Linter();
|
|
153
212
|
let replaced = text;
|
|
@@ -193,84 +252,60 @@ export default function Editor({
|
|
|
193
252
|
}
|
|
194
253
|
if (onEslintUpdate) onEslintUpdate(hints);
|
|
195
254
|
return hints;
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
255
|
+
});
|
|
256
|
+
} else if (format === 'json') {
|
|
257
|
+
return linter(view => {
|
|
258
|
+
if (!onEslintUpdate) return [];
|
|
259
|
+
const text = view.state.doc.toString();
|
|
260
|
+
const hints = [];
|
|
261
|
+
try {
|
|
262
|
+
JSON.parse(text);
|
|
263
|
+
} catch (e) {
|
|
264
|
+
if (e instanceof SyntaxError && 'message' in e) {
|
|
265
|
+
const match = e.message.match(/at position (\d+)/);
|
|
266
|
+
let pos = 0;
|
|
267
|
+
if (match) pos = parseInt(match[1], 10);
|
|
268
|
+
hints.push({
|
|
269
|
+
line: view.state.doc.lineAt(pos),
|
|
270
|
+
from: pos,
|
|
271
|
+
to: pos + 1,
|
|
272
|
+
severity: 'error',
|
|
273
|
+
message: e.message
|
|
274
|
+
});
|
|
210
275
|
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return result;
|
|
215
|
-
}, [languageExtension, eslintLinter, highlight.extension, keymapExtension, extensions, onScroll]);
|
|
216
|
-
const handleUpdate = viewUpdate => {
|
|
217
|
-
setFocused(viewUpdate.view.hasFocus);
|
|
218
|
-
if (onUpdate) onUpdate(viewUpdate);
|
|
219
|
-
};
|
|
220
|
-
const highlightKeyword = useCallback(() => {
|
|
221
|
-
if (!view || !keyword) return;
|
|
222
|
-
const {
|
|
223
|
-
effect,
|
|
224
|
-
decoration
|
|
225
|
-
} = highlight;
|
|
226
|
-
const cursor = isRegExp ? new RegExpCursor(view.state.doc, keyword) : new SearchCursor(view.state.doc, keyword, undefined, undefined, s => s.toLowerCase());
|
|
227
|
-
while (true) {
|
|
228
|
-
cursor.next();
|
|
229
|
-
if (cursor.done) break;
|
|
230
|
-
view.dispatch({
|
|
231
|
-
effects: effect.of([decoration.range(cursor.value.from, cursor.value.to)])
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}, [view, highlight, keyword, isRegExp]);
|
|
235
|
-
const scrollToBottom = useCallback(() => {
|
|
236
|
-
if (!view || !autoScrollToBottom) return;
|
|
237
|
-
view.dispatch({
|
|
238
|
-
effects: EditorView.scrollIntoView(view.state.doc.length)
|
|
276
|
+
}
|
|
277
|
+
onEslintUpdate(hints);
|
|
278
|
+
return hints;
|
|
239
279
|
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}), helperText && /*#__PURE__*/_jsx(FormHelperText, {
|
|
272
|
-
error: error,
|
|
273
|
-
children: helperText
|
|
274
|
-
})]
|
|
275
|
-
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function makeExtensions(languageExtension, eslintLinter, highlightExtension, keymapExtension, extensions, onScroll) {
|
|
283
|
+
const result = [EditorView.lineWrapping, highlightExtension, ...keymapExtension];
|
|
284
|
+
if (languageExtension) result.push(languageExtension);
|
|
285
|
+
if (eslintLinter) result.push(eslintLinter);
|
|
286
|
+
if (onScroll) {
|
|
287
|
+
result.push(EditorView.domEventHandlers({
|
|
288
|
+
scroll(event, view) {
|
|
289
|
+
if (!event.target?.className.includes('cm-scroller')) return;
|
|
290
|
+
onScroll(event, view.scrollDOM);
|
|
291
|
+
}
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
result.push(...extensions);
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
function getLanguageExtension(format) {
|
|
298
|
+
switch (format) {
|
|
299
|
+
case 'json':
|
|
300
|
+
return json();
|
|
301
|
+
case 'nscript':
|
|
302
|
+
return undefined;
|
|
303
|
+
default:
|
|
304
|
+
return javascript({
|
|
305
|
+
jsx: true
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function getKeymapExtension(keymap) {
|
|
310
|
+
return [keyMapper.of(keymap), keyMapper.of(defaultKeymap)];
|
|
276
311
|
}
|