Got the macro dialog working with previously selected macros in the rte and pre-fills the data that was previously there.

This commit is contained in:
Shannon
2013-09-24 18:46:53 +10:00
parent dbf9f70369
commit d3a9087093
6 changed files with 207 additions and 33 deletions

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Core.Macros
internal class MacroTagParser
{
private static readonly Regex MacroRteContent = new Regex(@"(<div class=[""']umb-macro-holder.+?[""'].*?>.*?<!--\s*?)(<\?UMBRACO_MACRO.*?/>)(.*?</div>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
private static readonly Regex MacroPersistedFormat = new Regex(@"<\?UMBRACO_MACRO macroAlias=[""'](\w+?)[""'].+?/>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO macroAlias=[""'](\w+?)[""'].+?)(?:/>|>.*?</\?UMBRACO_MACRO>)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
/// <summary>
/// This formats the persisted string to something useful for the rte so that the macro renders properly since we
@@ -33,10 +33,10 @@ namespace Umbraco.Core.Macros
{
return MacroPersistedFormat.Replace(persistedContent, match =>
{
if (match.Groups.Count >= 2)
if (match.Groups.Count >= 3)
{
//<div class="umb-macro-holder myMacro mceNonEditable">
var alias = match.Groups[1].Value;
var alias = match.Groups[2].Value;
var sb = new StringBuilder("<div class=\"umb-macro-holder ");
sb.Append(alias);
sb.Append(" mceNonEditable\"");
@@ -50,7 +50,8 @@ namespace Umbraco.Core.Macros
}
sb.AppendLine(">");
sb.Append("<!-- ");
sb.Append(match.Groups[0].Value);
sb.Append(match.Groups[1].Value.Trim());
sb.Append(" />");
sb.AppendLine(" -->");
sb.Append("Macro alias: ");
sb.Append("<strong>");

View File

@@ -24,6 +24,74 @@ Macro alias: <strong>Map</strong></div>
<p>asdfasdf</p>", result);
}
[Test]
public void Format_RTE_Data_For_Editor_Closing_Tag()
{
var content = @"<p>asdfasdf</p>
<p>asdfsadf</p>
<?UMBRACO_MACRO macroAlias=""Map"" ></?UMBRACO_MACRO>
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" /> -->
Macro alias: <strong>Map</strong></div>
<p>asdfasdf</p>", result);
}
[Test]
public void Format_RTE_Data_For_Editor_With_Params()
{
var content = @"<p>asdfasdf</p>
<p>asdfsadf</p>
<?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" />
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
Macro alias: <strong>Map</strong></div>
<p>asdfasdf</p>", result);
}
[Test]
public void Format_RTE_Data_For_Editor_With_Params_Closing_Tag()
{
var content = @"<p>asdfasdf</p>
<p>asdfsadf</p>
<?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" ></?UMBRACO_MACRO>
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
Macro alias: <strong>Map</strong></div>
<p>asdfasdf</p>", result);
}
[Test]
public void Format_RTE_Data_For_Editor_With_Params_Closing_Tag_And_Content()
{
var content = @"<p>asdfasdf</p>
<p>asdfsadf</p>
<?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" ><img src='blah.jpg'/></?UMBRACO_MACRO>
<p>asdfasdf</p>";
var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary<string, string>() { { "test1", "value1" }, { "test2", "value2" } });
Assert.AreEqual(@"<p>asdfasdf</p>
<p>asdfsadf</p>
<div class=""umb-macro-holder Map mceNonEditable"" test1=""value1"" test2=""value2"">
<!-- <?UMBRACO_MACRO macroAlias=""Map"" test1=""value1"" test2=""value2"" /> -->
Macro alias: <strong>Map</strong></div>
<p>asdfasdf</p>", result);
}
[Test]
public void Format_RTE_Data_For_Persistence()
{

View File

@@ -10,6 +10,31 @@ function macroService() {
return {
/** parses the special macro syntax like <?UMBRACO_MACRO macroAlias="Map" /> and returns an object with the macro alias and it's parameters */
parseMacroSyntax: function (syntax) {
var expression = /(<\?UMBRACO_MACRO macroAlias=["'](\w+?)["'].+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/im;
var match = expression.exec(syntax);
if (!match || match.length < 3) {
return null;
}
var alias = match[2];
//this will leave us with just the parameters
var paramsChunk = match[1].trim().replace(new RegExp("UMBRACO_MACRO macroAlias=[\"']" + alias + "[\"']"), "").trim();
var paramExpression = new RegExp("(\\w+?)=['\"](.*?)['\"]", "g");
var paramMatch;
var returnVal = {
alias: alias,
params: []
};
while (paramMatch = paramExpression.exec(paramsChunk)) {
returnVal.params.push({ alias: paramMatch[1], value: paramMatch[2] });
}
return returnVal;
},
/**
* @ngdoc function
* @name generateWebFormsSyntax
@@ -25,7 +50,7 @@ function macroService() {
// <?UMBRACO_MACRO macroAlias="BlogListPosts" />
var macroString = '<?UMBRACO_MACRO ';
var macroString = '<?UMBRACO_MACRO macroAlias=\"' + args.macroAlias + "\" ";
if (args.macroParams) {
for (var i = 0; i < args.macroParams.length; i++) {
@@ -35,7 +60,7 @@ function macroService() {
}
}
macroString += "macroAlias=\"" + args.macroAlias + "\" />";
macroString += "/>";
return 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) {
function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeout, macroResource, macroService) {
return {
/**
@@ -143,6 +143,25 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
});
/**
* Because the macro gets wrapped in a P tag because of the way 'enter' works, this
* method will return the macro element if not wrapped in a p, or the p if the macro
* element is the only one inside of it even if we are deep inside an element inside the macro
*/
function getRealMacroElem(element) {
var e = $(element).closest(".umb-macro-holder");
if (e.length > 0) {
if (e.get(0).parentNode.nodeName === "P") {
//now check if we're the only element
if (element.parentNode.childNodes.length === 1) {
return e.get(0).parentNode;
}
}
return e.get(0);
}
return null;
}
/** Adds the button instance */
editor.addButton('umbmacro', {
icon: 'custom icon-settings-alt',
@@ -152,25 +171,6 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
var ctrl = this;
var isOnMacroElement = false;
/**
* Because the macro gets wrapped in a P tag because of the way 'enter' works, this
* method will return the macro element if not wrapped in a p, or the p if the macro
* element is the only one inside of it even if we are deep inside an element inside the macro
*/
function getRealMacroElem(element) {
var e = $(element).closest(".umb-macro-holder");
if (e.length > 0) {
if (e.get(0).parentNode.nodeName === "P") {
//now check if we're the only element
if (element.parentNode.childNodes.length === 1) {
return e.get(0).parentNode;
}
}
return e.get(0);
}
return null;
}
/**
* 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
@@ -310,8 +310,32 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou
/** The insert macro button click event handler */
onclick: function () {
var dialogData;
//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.
var macroElement = editor.selection.getNode();
macroElement = getRealMacroElem(macroElement);
if (macroElement) {
//we have a macro selected so we'll need to parse it's alias and parameters
var contents = $(macroElement).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);
dialogData = {
macroData: parsed
};
}
dialogService.macroPicker({
scope: $scope,
dialogData : dialogData,
callback: function(data) {
//put the macro syntax in comments, we will parse this out on the server side to be used

View File

@@ -23,6 +23,19 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi
} else {
$scope.wizardStep = "paramSelect";
$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) {
var prop = _.find($scope.macroParams, function (item) {
return item.alias == p.alias;
});
if (prop) {
prop.value = p.value;
}
});
}
}
});
}
@@ -82,26 +95,35 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi
};
//here we check to see if we've been passed a selected macro and if so we'll set the
//editor to start with parameter editing
if ($scope.dialogData && $scope.dialogData.macroData) {
$scope.wizardStep = "paramSelect";
}
//get the macro list
entityResource.getAll("Macro")
.then(function (data) {
.then(function (data) {
$scope.macros = data;
//check if there's a pre-selected macro and if it exists
if ($scope.dialogData.selectedAlias) {
var found = _.find(data, function(item) {
return item.alias === $scope.dialogData.selectedAlias;
if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.alias) {
var found = _.find(data, function (item) {
return item.alias === $scope.dialogData.macroData.alias;
});
if (found) {
//select the macro and go to next screen
$scope.selectedMacro = found.id;
editParams();
return;
}
}
//we don't have a pre-selected macro so ensure the correct step is set
$scope.wizardStep = "macroSelect";
});
}
angular.module("umbraco").controller("Umbraco.Dialogs.InsertMacroController", InsertMacroController);

View File

@@ -7,6 +7,40 @@ describe('macro service tests', function () {
macroService = $injector.get('macroService');
}));
describe('generates macro syntax', function() {
it('can parse syntax for macros', 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");
});
it('can parse syntax for macros with body', 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");
});
});
describe('generates macro syntax', function () {
it('can generate syntax for macros', function () {
@@ -21,7 +55,7 @@ describe('macro service tests', function () {
});
expect(syntax).
toBe("<?UMBRACO_MACRO param1=\"value1\" param2=\"value2\" param3=\"value3\" macroAlias=\"myMacro\" />");
toBe("<?UMBRACO_MACRO macroAlias=\"myMacro\" param1=\"value1\" param2=\"value2\" param3=\"value3\" />");
});