This commit is contained in:
perploug
2013-09-25 15:17:21 +02:00
409 changed files with 1571 additions and 11604 deletions

View File

@@ -43,7 +43,8 @@ function valToggleMsg(serverValidationManager) {
}
});
scope.$on("saving", function(ev, args) {
//listen for the saving event (the result is a callback method which is called to unsubscribe)
var unsubscribeSaving = scope.$on("saving", function (ev, args) {
showValidation = true;
if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) {
element.show();
@@ -57,11 +58,20 @@ function valToggleMsg(serverValidationManager) {
}
});
scope.$on("saved", function (ev, args) {
//listen for the saved event (the result is a callback method which is called to unsubscribe)
var unsubscribeSaved = scope.$on("saved", function (ev, args) {
showValidation = false;
element.hide();
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise if this directive is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
element.bind('$destroy', function () {
unsubscribeSaving();
unsubscribeSaved();
});
}
};
}

View File

@@ -128,16 +128,29 @@ function entityResource($q, $http, umbRequestHelper) {
* </pre>
*
* @param {string} type Object type name
* @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server
* @param {string} postFilterParams optional parameters for the postFilter expression
* @returns {Promise} resourcePromise object containing the entity.
*
*/
getAll: function (type) {
getAll: function (type, postFilter, postFilterParams) {
//need to build the query string manually
var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : "");
if (postFilter && postFilterParams) {
var counter = 0;
_.each(postFilterParams, function(val, key) {
query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val;
counter++;
});
}
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetAll",
[{type: type }])),
query)),
'Failed to retreive entity data for type ' + type);
},

View File

@@ -40,15 +40,27 @@ function macroResource($q, $http, umbRequestHelper) {
*
* @param {int} macroId The macro id to get parameters for
* @param {int} pageId The current page id
* @param {Array} macroParamDictionary A dictionary of macro parameters
*
*/
getMacroResultAsHtmlForEditor: function (macroAlias, pageId) {
getMacroResultAsHtmlForEditor: function (macroAlias, pageId, macroParamDictionary) {
//need to format the query string for the custom dictionary
var query = "macroAlias=" + macroAlias + "&pageId=" + pageId;
if (macroParamDictionary) {
var counter = 0;
_.each(macroParamDictionary, function(val, key) {
query += "&macroParams[" + counter + "].key=" + key + "&macroParams[" + counter + "].value=" + val;
counter++;
});
}
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"macroApiBaseUrl",
"GetMacroResultAsHtmlForEditor",
[{ macroAlias: macroAlias }, { pageId: pageId }])),
query)),
'Failed to retreive macro result for macro with alias ' + macroAlias);
}

View File

@@ -26,11 +26,11 @@ function macroService() {
var paramExpression = new RegExp("(\\w+?)=['\"](.*?)['\"]", "g");
var paramMatch;
var returnVal = {
alias: alias,
params: []
macroAlias: alias,
marcoParamsDictionary: {}
};
while (paramMatch = paramExpression.exec(paramsChunk)) {
returnVal.params.push({ alias: paramMatch[1], value: paramMatch[2] });
returnVal.marcoParamsDictionary[paramMatch[1]] = paramMatch[2];
}
return returnVal;
},
@@ -52,12 +52,13 @@ function macroService() {
var macroString = '<?UMBRACO_MACRO macroAlias=\"' + args.macroAlias + "\" ";
if (args.macroParams) {
for (var i = 0; i < args.macroParams.length; i++) {
if (args.marcoParamsDictionary) {
var keyVal = args.macroParams[i].alias + "=\"" + (args.macroParams[i].value ? args.macroParams[i].value : "") + "\" ";
_.each(args.marcoParamsDictionary, function (val, key) {
var keyVal = key + "=\"" + (val ? val : "") + "\" ";
macroString += keyVal;
}
});
}
macroString += "/>";
@@ -80,12 +81,13 @@ function macroService() {
var macroString = '<umbraco:Macro ';
if (args.macroParams) {
for (var i = 0; i < args.macroParams.length; i++) {
var keyVal = args.macroParams[i].alias + "=\"" + (args.macroParams[i].value ? args.macroParams[i].value : "") + "\" ";
if (args.marcoParamsDictionary) {
_.each(args.marcoParamsDictionary, function (val, key) {
var keyVal = key + "=\"" + (val ? val : "") + "\" ";
macroString += keyVal;
}
});
}
macroString += "Alias=\"" + args.macroAlias + "\" runat=\"server\"></umbraco:Macro>";
@@ -108,19 +110,28 @@ function macroService() {
var macroString = "@Umbraco.RenderMacro(\"" + args.macroAlias + "\"";
if (args.macroParams && args.macroParams.length > 0) {
macroString += ", new {";
for (var i = 0; i < args.macroParams.length; i++) {
var keyVal = args.macroParams[i].alias + "=\"" + (args.macroParams[i].value ? args.macroParams[i].value : "") + "\"";
var hasParams = false;
var paramString;
if (args.marcoParamsDictionary) {
paramString = ", new {";
macroString += keyVal;
_.each(args.marcoParamsDictionary, function(val, key) {
hasParams = true;
if (i < args.macroParams.length - 1) {
macroString += ", ";
}
}
macroString += "}";
var keyVal = key + "=\"" + (val ? val : "") + "\", ";
paramString += keyVal;
});
//remove the last ,
paramString = paramString.trimEnd(", ");
paramString += "}";
}
if (hasParams) {
macroString += paramString;
}
macroString += ")";

View File

@@ -6,7 +6,7 @@
* @description
* A service containing all logic for all of the Umbraco TinyMCE plugins
*/
function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeout, macroResource, macroService) {
function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeout, macroResource, macroService, $routeParams) {
return {
/**
@@ -22,7 +22,7 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
*/
createInsertEmbeddedMedia: function (editor, $scope) {
editor.addButton('umbembeddialog', {
icon: 'media',
icon: 'custom icon-tv',
tooltip: 'Embed',
onclick: function () {
dialogService.embedDialog({
@@ -162,6 +162,41 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
return null;
}
/** loads in the macro content async from the server */
function loadMacroContent($macroDiv, macroData) {
//if we don't have the macroData, then we'll need to parse it from the macro div
if (!macroData) {
var contents = $macroDiv.contents();
var comment = _.find(contents, function (item) {
return item.nodeType === 8;
});
if (!comment) {
throw "Cannot parse the current macro, the syntax in the editor is invalid";
}
var syntax = comment.textContent.trim();
var parsed = macroService.parseMacroSyntax(syntax);
macroData = parsed;
}
var $ins = $macroDiv.find("ins");
//show the throbber
$macroDiv.addClass("loading");
var contentId = $routeParams.id;
macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.marcoParamsDictionary)
.then(function (htmlResult) {
$macroDiv.removeClass("loading");
htmlResult = htmlResult.trim();
if (htmlResult !== "") {
$ins.html(htmlResult);
}
});
}
/** Adds the button instance */
editor.addButton('umbmacro', {
icon: 'custom icon-settings-alt',
@@ -171,6 +206,57 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
var ctrl = this;
var isOnMacroElement = false;
/**
if the selection comes from a different element that is not the macro's
we need to check if the selection includes part of the macro, if so we'll force the selection
to clear to the next element since if people can select part of the macro markup they can then modify it.
*/
function handleSelectionChange() {
if (!editor.selection.isCollapsed()) {
var endSelection = tinymce.activeEditor.selection.getEnd();
var startSelection = tinymce.activeEditor.selection.getStart();
//don't proceed if it's an entire element selected
if (endSelection !== startSelection) {
//if the end selection is a macro then move the cursor
//NOTE: we don't have to handle when the selection comes from a previous parent because
// that is automatically taken care of with the normal onNodeChanged logic since the
// evt.element will be the macro once it becomes part of the selection.
var $testForMacro = $(endSelection).closest(".umb-macro-holder");
if ($testForMacro.length > 0) {
//it came from before so move after, if there is no after then select ourselves
var next = $testForMacro.next();
if (next.length > 0) {
editor.selection.setCursorLocation($testForMacro.next().get(0));
}
else {
selectMacroElement($testForMacro.get(0));
}
}
}
}
}
/** helper method to select the macro element */
function selectMacroElement(macroElement) {
// move selection to top element to ensure we can't edit this
editor.selection.select(macroElement);
// check if the current selection *is* the element (ie bug)
var currentSelection = editor.selection.getStart();
if (tinymce.isIE) {
if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) {
while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) {
currentSelection = currentSelection.parentNode;
}
editor.selection.select(currentSelection);
}
}
}
/**
* Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag.
* If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves
@@ -181,7 +267,10 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
//set our macro button active when on a node of class umb-macro-holder
var $macroElement = $(evt.element).closest(".umb-macro-holder");
handleSelectionChange();
//set the button active
ctrl.active($macroElement.length !== 0);
if ($macroElement.length > 0) {
@@ -189,20 +278,8 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
//remove the event listener before re-selecting
editor.off('NodeChange', onNodeChanged);
// move selection to top element to ensure we can't edit this
editor.selection.select(macroElement);
// check if the current selection *is* the element (ie bug)
var currentSelection = editor.selection.getStart();
if (tinymce.isIE) {
if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) {
while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) {
currentSelection = currentSelection.parentNode;
}
editor.selection.select(currentSelection);
}
}
selectMacroElement(macroElement);
//set the flag
isOnMacroElement = true;
@@ -216,6 +293,16 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
}
/** when the contents load we need to find any macros declared and load in their content */
editor.on("LoadContent", function (o) {
//get all macro divs and load their content
$(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function() {
loadMacroContent($(this));
});
});
/** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */
editor.on('BeforeExecCommand', function (o) {
if (isOnMacroElement) {
@@ -310,7 +397,10 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
/** The insert macro button click event handler */
onclick: function () {
var dialogData;
var dialogData = {
//flag for use in rte so we only show macros flagged for the editor
richTextEditor: true
};
//when we click we could have a macro already selected and in that case we'll want to edit the current parameters
//so we'll need to extract them and submit them to the dialog.
@@ -352,20 +442,9 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
editor.selection.setNode(macroDiv);
var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId));
var $ins = $macroDiv.find("ins");
//show the throbber
$macroDiv.addClass("loading");
macroResource.getMacroResultAsHtmlForEditor(data.macroAlias, 1234)
.then(function (htmlResult) {
$macroDiv.removeClass("loading");
htmlResult = htmlResult.trim();
if (htmlResult !== "") {
$ins.html(htmlResult);
}
});
//async load the macro content
loadMacroContent($macroDiv, data);
}
});

View File

@@ -25,13 +25,13 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi
$scope.macroParams = data;
//fill in the data if we are editing this macro
if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.params) {
_.each($scope.dialogData.macroData.params, function(p) {
if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.marcoParamsDictionary) {
_.each($scope.dialogData.macroData.marcoParamsDictionary, function (val, key) {
var prop = _.find($scope.macroParams, function (item) {
return item.alias == p.alias;
return item.alias == key;
});
if (prop) {
prop.value = p.value;
prop.value = val;
}
});
@@ -44,10 +44,13 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi
function submitForm() {
//collect the value data, close the dialog and send the data back to the caller
var vals = _.map($scope.macroParams, function (item) {
return { value: item.value, alias: item.alias };
});
//create a dictionary for the macro params
var paramDictionary = {};
_.each($scope.macroParams, function (item) {
paramDictionary[item.alias] = item.value;
});
//need to find the macro alias for the selected id
var macroAlias = $scope.selectedMacro.alias;
@@ -58,16 +61,16 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi
//get the syntax based on the rendering engine
var syntax;
if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "WebForms") {
syntax = macroService.generateWebFormsSyntax({ macroAlias: macroAlias, macroParams: vals });
syntax = macroService.generateWebFormsSyntax({ macroAlias: macroAlias, marcoParamsDictionary: paramDictionary });
}
else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === "Mvc") {
syntax = macroService.generateMvcSyntax({ macroAlias: macroAlias, macroParams: vals });
syntax = macroService.generateMvcSyntax({ macroAlias: macroAlias, marcoParamsDictionary: paramDictionary });
}
else {
syntax = macroService.generateMacroSyntax({ macroAlias: macroAlias, macroParams: vals });
syntax = macroService.generateMacroSyntax({ macroAlias: macroAlias, marcoParamsDictionary: paramDictionary });
}
$scope.submit({syntax : syntax, macroAlias: macroAlias, macroParams: vals });
$scope.submit({ syntax: syntax, macroAlias: macroAlias, marcoParamsDictionary: paramDictionary });
}
$scope.macros = [];
@@ -103,16 +106,16 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi
$scope.wizardStep = "paramSelect";
}
//get the macro list
entityResource.getAll("Macro")
//get the macro list - pass in a filter if it is only for rte
entityResource.getAll("Macro", ($scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true) ? "UseInEditor=true" : null)
.then(function (data) {
$scope.macros = data;
//check if there's a pre-selected macro and if it exists
if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.alias) {
if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) {
var found = _.find(data, function (item) {
return item.alias === $scope.dialogData.macroData.alias;
return item.alias === $scope.dialogData.macroData.macroAlias;
});
if (found) {
//select the macro and go to next screen

View File

@@ -1,32 +1,39 @@
<div class="umb-panel" ng-controller="Umbraco.Dialogs.RteEmbedController">
<form ng-controller="Umbraco.Dialogs.RteEmbedController">
<div class="umb-panel" val-show-validation>
<div class="umb-panel-header">
<div class="umb-el-wrap umb-panel-buttons">
<div class="btn-toolbar umb-btn-toolbar">
<input type="button" ng-click="insert()" class="btn btn-primary" value="Insert" ng-disabled="!success" />
<div class="umb-panel-header">
<div class="umb-el-wrap umb-panel-buttons">
<div class="btn-toolbar umb-btn-toolbar">
<button type="button" class="btn" ng-click="close()">Cancel</button>
<input type="button" ng-click="insert()" class="btn btn-primary" value="Ok" ng-disabled="!success" />
</div>
</div>
</div>
</div>
<div class="umb-panel-body umb-scrollable" auto-scale="1">
<div class="umb-panel">
<div class="umb-control-group">
<label for="url">Url</label>
<input id="url" type="text" ng-model="url" ng-change="showPreview()"/>
<div ng-show="supportsDimensions">
<label>Size:</label>
<input type="text" ng-model="width" on-blur="changeSize('width')"/> x <input type="text" ng-model="height" on-blur="changeSize('height')"/>
<label for="constrain">Constrain:</label>
<input id="constrain" type="checkbox" ng-model="constrain"/>
<div class="umb-panel-body umb-scrollable" auto-scale="1">
<div class="umb-control-group">
<div class="umb-pane">
<label for="url">Url:</label>
<input id="url" type="text" ng-model="url" ng-change="showPreview()" required/>
</div>
<div class="umb-pane" ng-show="supportsDimensions">
<label>Size:</label>
<input type="text" ng-model="width" on-blur="changeSize('width')" style="width:50px"/> x <input type="text" ng-model="height" on-blur="changeSize('height')" style="width:50px"/>
<label for="constrain" style="display:inline">Constrain:</label>
<input id="constrain" type="checkbox" ng-model="constrain" style="margin-bottom: 10px;"/>
</div>
<div class="umb-pane">
<p ng-bind="info"></p>
<div ng-bind-html-unsafe="preview">
</div>
</div>
</div>
<p ng-bind="info"></p>
<div ng-bind-html-unsafe="preview">
</div>
</div>
</div>
</div>
</div>
</div>
</form>

View File

@@ -1,6 +1,6 @@
angular.module("umbraco")
.controller("Umbraco.Editors.RTEController",
function ($rootScope, $scope, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService) {
function ($rootScope, $element, $scope, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper) {
//TODO: This should be configurable (i.e. from the config file we have and/or from pre-values)
var validElements = "@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|"
@@ -23,7 +23,7 @@ angular.module("umbraco")
+ "input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value],"
+ "kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],"
+ "q[cite],samp,select[disabled|multiple|name|size],small,"
+ "textarea[cols|rows|disabled|name|readonly],tt,var,big";
+ "textarea[cols|rows|disabled|name|readonly],tt,var,big,iframe[*]";
//TODO: This should be configurable (i.e. from the config file we have and/or from pre-values)
var toolbar = "code | bold italic | styleselect | alignleft aligncenter alignright | bullist numlist | outdent indent | link image umbmediapicker umbiconpicker umbembeddialog umbmacro";
@@ -47,11 +47,36 @@ angular.module("umbraco")
height: 340,
toolbar: toolbar,
setup: function (editor) {
//We need to listen on multiple things here because of the nature of tinymce, it doesn't
//fire events when you think!
//The change event doesn't fire when content changes, only when cursor points are changed and undo points
//are created. the blur event doesn't fire if you insert content into the editor with a button and then
//press save.
//We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
//listen to both change and blur and also on our own 'saving' event. I think this will be best because a
//timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
//save before the timeout elapsed.
editor.on('change', function (e) {
angularHelper.safeApply($scope, function() {
$scope.model.value = editor.getContent();
});
});
editor.on('blur', function (e) {
$scope.$apply(function () {
angularHelper.safeApply($scope, function () {
$scope.model.value = editor.getContent();
});
});
var unsubscribe = $scope.$on("saving", function() {
$scope.model.value = editor.getContent();
});
//when the element is disposed we need to unsubscribe!
// NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
// element might still be there even after the modal has been hidden.
$element.bind('$destroy', function () {
unsubscribe();
});
//Create the insert media plugin
tinyMceService.createMediaPicker(editor, $scope);

View File

@@ -14,12 +14,11 @@ describe('macro service tests', function () {
var result = macroService.parseMacroSyntax("<?UMBRACO_MACRO macroAlias='Map' test1=\"asdf\" test2='hello' />");
expect(result).not.toBeNull();
expect(result.alias).toBe("Map");
expect(result.params.length).toBe(2);
expect(result.params[0].alias).toBe("test1");
expect(result.params[0].value).toBe("asdf");
expect(result.params[1].alias).toBe("test2");
expect(result.params[1].value).toBe("hello");
expect(result.macroAlias).toBe("Map");
expect(result.marcoParamsDictionary.test1).not.toBeUndefined();
expect(result.marcoParamsDictionary.test1).toBe("asdf");
expect(result.marcoParamsDictionary.test2).not.toBeUndefined();
expect(result.marcoParamsDictionary.test2).toBe("hello");
});
@@ -29,12 +28,11 @@ describe('macro service tests', function () {
var result = macroService.parseMacroSyntax("<?UMBRACO_MACRO macroAlias='Map' test1=\"asdf\" test2='hello' ><img src='blah.jpg'/></?UMBRACO_MACRO>");
expect(result).not.toBeNull();
expect(result.alias).toBe("Map");
expect(result.params.length).toBe(2);
expect(result.params[0].alias).toBe("test1");
expect(result.params[0].value).toBe("asdf");
expect(result.params[1].alias).toBe("test2");
expect(result.params[1].value).toBe("hello");
expect(result.macroAlias).toBe("Map");
expect(result.marcoParamsDictionary.test1).not.toBeUndefined();
expect(result.marcoParamsDictionary.test1).toBe("asdf");
expect(result.marcoParamsDictionary.test2).not.toBeUndefined();
expect(result.marcoParamsDictionary.test2).toBe("hello");
});
@@ -47,11 +45,11 @@ describe('macro service tests', function () {
var syntax = macroService.generateMacroSyntax({
macroAlias: "myMacro",
macroParams: [
{ alias: "param1", value: "value1" },
{ alias: "param2", value: "value2" },
{ alias: "param3", value: "value3" }
]
marcoParamsDictionary: {
param1: "value1",
param2: "value2",
param3: "value3"
}
});
expect(syntax).
@@ -63,7 +61,7 @@ describe('macro service tests', function () {
var syntax = macroService.generateMacroSyntax({
macroAlias: "myMacro",
macroParams: []
marcoParamsDictionary: {}
});
expect(syntax).
@@ -75,11 +73,11 @@ describe('macro service tests', function () {
var syntax = macroService.generateWebFormsSyntax({
macroAlias: "myMacro",
macroParams: [
{ alias: "param1", value: "value1" },
{ alias: "param2", value: "value2" },
{ alias: "param3", value: "value3" }
]
marcoParamsDictionary: {
param1: "value1",
param2: "value2",
param3: "value3"
}
});
expect(syntax).
@@ -91,7 +89,7 @@ describe('macro service tests', function () {
var syntax = macroService.generateWebFormsSyntax({
macroAlias: "myMacro",
macroParams: []
marcoParamsDictionary: {}
});
expect(syntax).
@@ -103,11 +101,11 @@ describe('macro service tests', function () {
var syntax = macroService.generateMvcSyntax({
macroAlias: "myMacro",
macroParams: [
{ alias: "param1", value: "value1" },
{ alias: "param2", value: "value2" },
{ alias: "param3", value: "value3" }
]
marcoParamsDictionary: {
param1: "value1",
param2: "value2",
param3: "value3"
}
});
expect(syntax).
@@ -116,11 +114,11 @@ describe('macro service tests', function () {
});
it('can generate syntax for webforms with no params', function () {
it('can generate syntax for MVC with no params', function () {
var syntax = macroService.generateMvcSyntax({
macroAlias: "myMacro",
macroParams: []
marcoParamsDictionary: {}
});
expect(syntax).