changed formula editor to use codeeditor, added support for object autocomplete in the code editor
This commit is contained in:
1 parent
8fe28e68f9
commit
01838ceecc
6 files changed
+131
-263
No files matched your search
@@ -6,7 +6,6 @@ function newFoundationsCodeEditorSpace() {
|
||||
container: undefined,
|
||||
editorPage: undefined,
|
||||
openSpaceArea: openSpaceArea,
|
||||
// navigateTo: navigateTo,
|
||||
physics: physics,
|
||||
draw: draw,
|
||||
getContainer: getContainer,
|
||||
|
||||
@@ -66,7 +66,7 @@ function newFoundationsCodeEditorEditorPage() {
|
||||
monacoEditor = undefined
|
||||
}
|
||||
|
||||
function render(originatingNode, rootNodes) {
|
||||
function render(originatingNode) {
|
||||
|
||||
thisObject.originatingNode = originatingNode
|
||||
intellisenseModels = []
|
||||
@@ -93,6 +93,9 @@ function newFoundationsCodeEditorEditorPage() {
|
||||
monaco.editor.createModel(codeString, "javascript")
|
||||
monaco.editor.createModel(recordFormulaString, "javascript")
|
||||
|
||||
showAutocompletionFor(tradingEngineObj)
|
||||
|
||||
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true)
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
@@ -109,70 +112,145 @@ function newFoundationsCodeEditorEditorPage() {
|
||||
monacoEditor.getModel().onDidChangeContent(function () {
|
||||
originatingNode.payload.node.code = monacoEditor.getValue()
|
||||
})
|
||||
|
||||
|
||||
|
||||
function showAutocompletionFor(obj) {
|
||||
// Disable default autocompletion for javascript
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ noLib: true });
|
||||
|
||||
// Helper function to return the monaco completion item type of a thing
|
||||
function getType(thing, isMember) {
|
||||
isMember = (isMember === undefined) ? (typeof isMember == "boolean") ? isMember : false : false; // Give isMember a default value of false
|
||||
|
||||
switch ((typeof thing).toLowerCase()) {
|
||||
case "object":
|
||||
return monaco.languages.CompletionItemKind.Class;
|
||||
|
||||
case "function":
|
||||
return (isMember) ? monaco.languages.CompletionItemKind.Method : monaco.languages.CompletionItemKind.Function;
|
||||
|
||||
default:
|
||||
return (isMember) ? monaco.languages.CompletionItemKind.Property : monaco.languages.CompletionItemKind.letiable;
|
||||
}
|
||||
}
|
||||
|
||||
// Register object that will return autocomplete items
|
||||
monaco.languages.registerCompletionItemProvider('javascript', {
|
||||
// Run this function when the period or open parenthesis is typed (and anything after a space)
|
||||
triggerCharacters: ['.', '('],
|
||||
|
||||
// Function to generate autocompletion results
|
||||
provideCompletionItems: function(model, position, token) {
|
||||
// Split everything the user has typed on the current line up at each space, and only look at the last word
|
||||
let last_chars = model.getValueInRange({startLineNumber: position.lineNumber, startColumn: 0, endLineNumber: position.lineNumber, endColumn: position.column});
|
||||
let words = last_chars.replace("\t", "").split(" ");
|
||||
let active_typing = words[words.length - 1]; // What the user is currently typing (everything after the last space)
|
||||
if (active_typing.includes('(')) {
|
||||
let split = active_typing.split('(');
|
||||
active_typing = split[split.length-1]
|
||||
}
|
||||
|
||||
// If the last character typed is a period then we need to look at member objects of the obj object
|
||||
let is_member = active_typing.charAt(active_typing.length - 1) === ".";
|
||||
|
||||
// Array of autocompletion results
|
||||
let result = [];
|
||||
|
||||
// Used for generic handling between member and non-member objects
|
||||
let last_token = obj;
|
||||
let prefix = '';
|
||||
|
||||
if (is_member) {
|
||||
// Is a member, get a list of all members, and the prefix
|
||||
let parents = active_typing.substring(0, active_typing.length - 1).split(".");
|
||||
last_token = obj[parents[0]];
|
||||
prefix = parents[0];
|
||||
|
||||
// Loop through all the parents the current one will have (to generate prefix)
|
||||
for (let i = 1; i < parents.length; i++) {
|
||||
if (last_token.hasOwnProperty(parents[i])) {
|
||||
prefix += '.' + parents[i];
|
||||
last_token = last_token[parents[i]];
|
||||
} else {
|
||||
// Not valid
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
prefix += '.';
|
||||
}
|
||||
|
||||
// Get all the child properties of the last token
|
||||
for (let prop in last_token) {
|
||||
// Do not show properites that begin with "__"
|
||||
if (last_token.hasOwnProperty(prop) && !prop.startsWith("__")) {
|
||||
// Get the detail type (try-catch) incase object does not have prototype
|
||||
let details = '';
|
||||
try {
|
||||
details = last_token[prop].__proto__.constructor.name;
|
||||
} catch (e) {
|
||||
details = typeof last_token[prop];
|
||||
}
|
||||
|
||||
// Create completion object
|
||||
let to_push = {
|
||||
label: prefix + prop,
|
||||
kind: getType(last_token[prop], is_member),
|
||||
detail: details,
|
||||
insertText: prop
|
||||
};
|
||||
|
||||
// Change insertText and documentation for functions
|
||||
if (to_push.detail.toLowerCase() === 'function') {
|
||||
to_push.insertText += "(";
|
||||
to_push.documentation = (last_token[prop].toString()).split("{")[0]; // Show function prototype in the documentation popup
|
||||
}
|
||||
|
||||
// Add to final results
|
||||
result.push(to_push);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: result
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The autocomplete models will be based on:
|
||||
* 1. all data mines loaded into workspace
|
||||
* 2. trading engine loaded into the workspace
|
||||
* 1. all data mines loaded into workspace - by creating a model in monaco (basically is like opening a file containing all the variables in vs-code, thus it will make them available in other files as well)
|
||||
* 2. trading engine loaded into the workspace - we create an obj. representation using getProtocolNode and we are using monaco registerCompletionItemProvider
|
||||
*
|
||||
* TODO: It is compulsory that at one moment in time we should add proper definitions for our variables (to mimic some framework), but this is a different task and can be improved over time
|
||||
* TODO: It is recommended that for extending it to read the monaco editor docs: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.completionitemprovider.html and https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example
|
||||
* TODO: Right now the intellisense model is rather primitive being built on existing code available in the workspace without having any definitions, but strong enough to provide accurate autocompletion
|
||||
*/
|
||||
function buildIntelliSenseModels() {
|
||||
|
||||
UI.projects.foundations.spaces.designSpace.workspace.workspaceNode.rootNodes.forEach(node => {
|
||||
if (node.type === 'Data Mine') {
|
||||
//Getting all the Javascript code into one single variable
|
||||
//Getting all the Javascript code into one single letiable
|
||||
UI.projects.visualScripting.utilities.branches.nodeBranchToArray(node, 'Procedure Javascript Code').forEach(node => {
|
||||
codeString += '\n'
|
||||
codeString += node.code
|
||||
})
|
||||
//Getting all the Formula code into one single variable
|
||||
//Getting all the Formula code into one single letiable
|
||||
UI.projects.visualScripting.utilities.branches.nodeBranchToArray(node, 'Record Formula').forEach(node => {
|
||||
recordFormulaString += '\n'
|
||||
recordFormulaString += node.code
|
||||
})
|
||||
} else if (node.type === 'Trading Engine') {
|
||||
// Create an object representation for
|
||||
tradingEngineObj = UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node, false, true, true, false, false, undefined)
|
||||
tradingEngineObj = {tradingEngine : UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node, false, false, false, false, false, undefined)}
|
||||
}
|
||||
})
|
||||
|
||||
console.log(codeString)
|
||||
console.log(recordFormulaString)
|
||||
|
||||
function extractCodeValues(node) {
|
||||
|
||||
}
|
||||
|
||||
function stepBackUntilNodeType(originatingNode, nodeType) {
|
||||
if (originatingNode === undefined) {
|
||||
return
|
||||
}
|
||||
if (nodeType === undefined) {
|
||||
return
|
||||
}
|
||||
if (originatingNode.payload === undefined) {
|
||||
return
|
||||
}
|
||||
if (originatingNode.payload.parentNode === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let parent = originatingNode.payload.parentNode;
|
||||
if (parent.type === nodeType) {
|
||||
console.log(parent)
|
||||
return parent
|
||||
} else {
|
||||
stepBackUntilNodeType(parent, nodeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createCodePath() {
|
||||
let parentNodeStructure = []
|
||||
@@ -202,5 +280,4 @@ function newFoundationsCodeEditorEditorPage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
*
|
||||
* The only reason we are keeping this class is to keep consistency across the uiObject implementation
|
||||
* It will also provide a good place to enrich the action received, separating the logic of the action from the editor space
|
||||
* Entire functionality is provided by CodeEditor Space
|
||||
*/
|
||||
function newCodeEditor() {
|
||||
|
||||
@@ -1,200 +1,30 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* The only reason we are keeping this class is to keep consistency across the uiObject implementation when it comes to action handlers
|
||||
* It will also provide a good place to enrich the action received, separating the logic of the action from the editor space
|
||||
* Entire functionality is provided by CodeEditor Space
|
||||
*/
|
||||
function newFormulaEditor() {
|
||||
const MODULE_NAME = 'Formula Editor'
|
||||
|
||||
let thisObject = {
|
||||
isVisibleFunction: undefined,
|
||||
visible: false,
|
||||
imagePathOK: undefined,
|
||||
imagePathNOT_OK: undefined,
|
||||
rawRadius: undefined,
|
||||
targetRadius: undefined,
|
||||
currentRadius: undefined,
|
||||
container: undefined,
|
||||
payload: undefined,
|
||||
deactivate: deactivate,
|
||||
activate: activate,
|
||||
physics: physics,
|
||||
drawBackground: drawBackground,
|
||||
drawForeground: drawForeground,
|
||||
getContainer: getContainer,
|
||||
finalize: finalize,
|
||||
initialize: initialize
|
||||
}
|
||||
|
||||
thisObject.container = newContainer()
|
||||
thisObject.container.initialize(MODULE_NAME)
|
||||
thisObject.container.isClickeable = false
|
||||
thisObject.container.isDraggeable = false
|
||||
thisObject.container.detectMouseOver = false
|
||||
thisObject.container.frame.radius = 0
|
||||
thisObject.container.frame.position.x = 0
|
||||
thisObject.container.frame.position.y = 0
|
||||
|
||||
let isMouseOver = false
|
||||
let SIZE_FACTOR = 1.0
|
||||
|
||||
return thisObject
|
||||
|
||||
|
||||
function finalize() {
|
||||
thisObject.container.finalize()
|
||||
thisObject.container = undefined
|
||||
thisObject.payload = undefined
|
||||
thisObject.isVisibleFunction = undefined
|
||||
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
}
|
||||
|
||||
function deactivate() {
|
||||
if (thisObject.visible === true) {
|
||||
thisObject.visible = false
|
||||
let textArea = document.getElementById('textArea')
|
||||
textArea.style.display = 'none'
|
||||
thisObject.payload.node.code = textArea.value
|
||||
}
|
||||
EDITOR_ON_FOCUS = false
|
||||
}
|
||||
|
||||
function activate(action) {
|
||||
thisObject.visible = true
|
||||
thisObject.payload = action.node.payload
|
||||
thisObject.rawRadius = 8
|
||||
thisObject.targetRadius = thisObject.container.frame.radius
|
||||
thisObject.currentRadius = 0
|
||||
thisObject.payload.uiObject.resetErrorMessage()
|
||||
thisObject.payload.uiObject.uiObjectTitle.exitEditMode()
|
||||
|
||||
let textArea = document.getElementById('textArea')
|
||||
textArea.value = thisObject.payload.node.code
|
||||
textArea.style = 'resize: none;' +
|
||||
' border: none;' +
|
||||
' outline: none;' +
|
||||
'box-shadow: none;' +
|
||||
'overflow:hidden;' +
|
||||
'font-family: ' + UI_FONT.PRIMARY + ';' +
|
||||
'font-size: 14px;' +
|
||||
'background-color: rgb(' + UI_COLOR.MANGANESE_PURPLE + ');' +
|
||||
'color:rgb(255, 255, 255);' +
|
||||
'width: ' + thisObject.container.frame.width + 'px;' +
|
||||
'height: ' + thisObject.container.frame.height + 'px'
|
||||
textArea.style.display = 'block'
|
||||
textArea.focus()
|
||||
EDITOR_ON_FOCUS = true
|
||||
}
|
||||
|
||||
function getContainer(point) {
|
||||
let container
|
||||
if (thisObject.visible === true) {
|
||||
if (point.x === VERY_LARGE_NUMBER) {
|
||||
/* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that
|
||||
anyone can react. In our case, the code editor has a text area that is not part of the canvas, so the event is
|
||||
triggered. We compensate recognizing this coordinate and returning our container. */
|
||||
return thisObject.container
|
||||
}
|
||||
|
||||
if (thisObject.container.frame.isThisPointHere(point, true, false) === true) {
|
||||
return thisObject.container
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function physics() {
|
||||
if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= 0.5) {
|
||||
if (thisObject.currentRadius < thisObject.targetRadius) {
|
||||
thisObject.currentRadius = thisObject.currentRadius + 0.5
|
||||
} else {
|
||||
thisObject.currentRadius = thisObject.currentRadius - 0.5
|
||||
}
|
||||
}
|
||||
|
||||
thisObject.container.frame.position.x = 0
|
||||
thisObject.container.frame.position.y = 0
|
||||
|
||||
thisObject.container.frame.width = thisObject.container.frame.radius * 1 * SIZE_FACTOR
|
||||
thisObject.container.frame.height = thisObject.container.frame.radius * 1 * SIZE_FACTOR
|
||||
|
||||
let textAreaPosition = {
|
||||
x: 0 - thisObject.container.frame.width / 2,
|
||||
y: 0 - thisObject.container.frame.height * 3 / 7 + CURRENT_TOP_MARGIN
|
||||
}
|
||||
|
||||
textAreaPosition = thisObject.container.frame.frameThisPoint(textAreaPosition)
|
||||
if (thisObject.visible === true) {
|
||||
let checkPosition = {
|
||||
x: textAreaPosition.x,
|
||||
y: textAreaPosition.y - CURRENT_TOP_MARGIN
|
||||
}
|
||||
if (thisObject.isVisibleFunction(checkPosition) === false) {
|
||||
deactivate()
|
||||
}
|
||||
}
|
||||
if (thisObject.visible === true) {
|
||||
let textAreaDiv = document.getElementById('textAreaDiv')
|
||||
textAreaDiv.style = 'position:fixed; top:' + textAreaPosition.y + 'px; left:' + textAreaPosition.x + 'px; z-index:1; '
|
||||
}
|
||||
}
|
||||
|
||||
function drawBackground() {
|
||||
if (thisObject.visible === true) {
|
||||
let position = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
position = thisObject.container.frame.frameThisPoint(position)
|
||||
|
||||
let radius = thisObject.container.frame.radius * SIZE_FACTOR
|
||||
|
||||
if (radius > 0.5) {
|
||||
browserCanvasContext.beginPath()
|
||||
browserCanvasContext.arc(position.x, position.y, radius + 3, 0, Math.PI * 2, true)
|
||||
browserCanvasContext.closePath()
|
||||
browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')'
|
||||
browserCanvasContext.fill()
|
||||
|
||||
browserCanvasContext.beginPath()
|
||||
browserCanvasContext.arc(position.x, position.y, radius , 0, Math.PI * 2, true)
|
||||
browserCanvasContext.closePath()
|
||||
browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.MANGANESE_PURPLE + ', ' + 1 + ')'
|
||||
browserCanvasContext.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
function drawForeground() {
|
||||
let iconPosition = {
|
||||
x: 0,
|
||||
y: thisObject.currentRadius * 1.5
|
||||
}
|
||||
|
||||
iconPosition = thisObject.container.frame.frameThisPoint(iconPosition)
|
||||
|
||||
/* Menu Item */
|
||||
|
||||
if (thisObject.canDrawIcon === true && thisObject.currentRadius > 1) {
|
||||
browserCanvasContext.drawImage(thisObject.icon, iconPosition.x - thisObject.currentRadius, iconPosition.y - thisObject.currentRadius, thisObject.currentRadius * 2, thisObject.currentRadius * 2)
|
||||
|
||||
/* Menu Label */
|
||||
|
||||
let label = 'Code looks good!'
|
||||
let labelPoint
|
||||
let fontSize = 10
|
||||
|
||||
browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY
|
||||
|
||||
if (thisObject.currentRadius >= thisObject.targetRadius) {
|
||||
labelPoint = {
|
||||
x: iconPosition.x + thisObject.currentRadius + 10,
|
||||
y: iconPosition.y + fontSize * FONT_ASPECT_RATIO
|
||||
}
|
||||
|
||||
browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY
|
||||
browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)'
|
||||
browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y)
|
||||
}
|
||||
}
|
||||
UI.projects.foundations.spaces.codeEditorSpace.openSpaceArea(action.node)
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ function newUiObject() {
|
||||
payload: undefined,
|
||||
configEditor: undefined,
|
||||
conditionEditor: undefined,
|
||||
formulaEditor: undefined,
|
||||
listSelector: undefined,
|
||||
uiObjectTitle: undefined,
|
||||
icon: undefined,
|
||||
@@ -202,10 +201,6 @@ function newUiObject() {
|
||||
thisObject.conditionEditor = undefined
|
||||
}
|
||||
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
thisObject.formulaEditor.finalize()
|
||||
thisObject.formulaEditor = undefined
|
||||
}
|
||||
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
thisObject.listSelector.finalize()
|
||||
@@ -303,11 +298,6 @@ function newUiObject() {
|
||||
if (container !== undefined) { return container }
|
||||
}
|
||||
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
container = thisObject.formulaEditor.getContainer(point)
|
||||
if (container !== undefined) { return container }
|
||||
}
|
||||
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
container = thisObject.listSelector.getContainer(point)
|
||||
if (container !== undefined) { return container }
|
||||
@@ -326,11 +316,6 @@ function newUiObject() {
|
||||
getitle = false
|
||||
}
|
||||
}
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
if (thisObject.formulaEditor.visible === true) {
|
||||
getitle = false
|
||||
}
|
||||
}
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
if (thisObject.listSelector.visible === true) {
|
||||
getitle = false
|
||||
@@ -380,9 +365,6 @@ function newUiObject() {
|
||||
thisObject.conditionEditor.physics()
|
||||
}
|
||||
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
thisObject.formulaEditor.physics()
|
||||
}
|
||||
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
thisObject.listSelector.physics()
|
||||
@@ -1354,9 +1336,7 @@ function newUiObject() {
|
||||
if (thisObject.conditionEditor !== undefined) {
|
||||
thisObject.conditionEditor.deactivate()
|
||||
}
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
thisObject.formulaEditor.deactivate()
|
||||
}
|
||||
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
thisObject.listSelector.deactivate()
|
||||
}
|
||||
@@ -1383,9 +1363,6 @@ function newUiObject() {
|
||||
if (thisObject.conditionEditor !== undefined) {
|
||||
thisObject.conditionEditor.deactivate()
|
||||
}
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
thisObject.formulaEditor.deactivate()
|
||||
}
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
thisObject.listSelector.deactivate()
|
||||
}
|
||||
@@ -1461,18 +1438,12 @@ function newUiObject() {
|
||||
drawChainLine()
|
||||
|
||||
|
||||
if (thisObject.configEditor !== undefined) {
|
||||
thisObject.configEditor.drawBackground()
|
||||
thisObject.configEditor.drawForeground()
|
||||
}
|
||||
|
||||
if (thisObject.conditionEditor !== undefined) {
|
||||
thisObject.conditionEditor.drawBackground()
|
||||
thisObject.conditionEditor.drawForeground()
|
||||
}
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
thisObject.formulaEditor.drawBackground()
|
||||
thisObject.formulaEditor.drawForeground()
|
||||
}
|
||||
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
thisObject.listSelector.drawBackground()
|
||||
thisObject.listSelector.drawForeground()
|
||||
@@ -1494,12 +1465,7 @@ function newUiObject() {
|
||||
drawTitle = false
|
||||
}
|
||||
}
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
if (thisObject.formulaEditor.visible === true) {
|
||||
drawMenu = false
|
||||
drawTitle = false
|
||||
}
|
||||
}
|
||||
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
if (thisObject.listSelector.visible === true) {
|
||||
drawMenu = false
|
||||
@@ -1678,9 +1644,6 @@ function newUiObject() {
|
||||
if (thisObject.conditionEditor.visible === true) { return true }
|
||||
}
|
||||
|
||||
if (thisObject.formulaEditor !== undefined) {
|
||||
if (thisObject.formulaEditor.visible === true) { return true }
|
||||
}
|
||||
if (thisObject.listSelector !== undefined) {
|
||||
if (thisObject.listSelector.visible === true) { return true }
|
||||
}
|
||||
|
||||
@@ -484,8 +484,6 @@ function newUiObjectConstructor() {
|
||||
}
|
||||
if (schemaDocument.editors.formula === true) {
|
||||
uiObject.formulaEditor = newFormulaEditor()
|
||||
uiObject.formulaEditor.isVisibleFunction = uiObject.isVisibleFunction
|
||||
uiObject.formulaEditor.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false)
|
||||
uiObject.formulaEditor.initialize()
|
||||
}
|
||||
if (schemaDocument.editors.list === true) {
|
||||
|
||||
Reference in new issue
Block a user