diff --git a/README.md b/README.md
index cb8461c69..6874e367a 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ There are many ways you can [contribute to Orchard](http://orchardproject.net/co
* [Participate in our gitter.im chatroom](https://gitter.im/OrchardCMS/Orchard)
* [Participate in forum discussions](http://orchard.codeplex.com/discussions)
* [Submit a pull request](http://docs.orchardproject.net/Documentation/Contributing-patches)
-* [Translate Orchard](http://orchardproject.net/localize)
+* [Translate Orchard](http://orchardproject.net/localization)
* [Contribute modules and themes to our gallery](http://gallery.orchardproject.net/)
* [Send us feedback](mailto:ofeedbk@microsoft.com)
diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
index 4a39036af..02ed1d16f 100644
--- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj
+++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
@@ -320,6 +320,7 @@
+
diff --git a/src/Orchard.Tests/Services/YamlParserTests.cs b/src/Orchard.Tests/Services/YamlParserTests.cs
new file mode 100644
index 000000000..19ece43fa
--- /dev/null
+++ b/src/Orchard.Tests/Services/YamlParserTests.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Newtonsoft.Json.Linq;
+using Orchard.Services;
+
+namespace Orchard.Tests.Services {
+
+ [TestFixture]
+ public class YamlParserTests {
+
+ [Test]
+ public void ShouldConvertYamlToWellknowType() {
+ var parser = new YamlParser();
+ var yaml = SampleYamlDocument;
+
+ var order = parser.Deserialize(yaml);
+
+ Assert.AreEqual("Nikola", order.Customer.FirstName);
+ Assert.AreEqual(2, order.Items.Count);
+ }
+
+ [Test]
+ public void ShouldConvertYamlToDynamic()
+ {
+ var parser = new YamlParser();
+ var yaml = SampleYamlDocument;
+
+ var order = parser.Deserialize(yaml);
+
+ Assert.AreEqual("Nikola", (string)order.Customer.FirstName);
+ Assert.AreEqual(2, (int)order.Items.Count);
+ }
+
+ public class Order {
+ public DateTime Date { get; set; }
+ public Customer Customer { get; set; }
+ public IList Items { get; set; }
+ }
+
+ public class OrderItem {
+ public string Product { get; set; }
+ public int Quantity { get; set; }
+ public decimal Price { get; set; }
+ }
+
+ public class Customer {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+
+ }
+
+ private const string SampleYamlDocument =
+@"
+Date: 1916-04-01
+Customer:
+ FirstName: Nikola
+ LastName: Tesla
+Items:
+ - Product: Bulb
+ Quantity: 1
+ Price: 1.46
+
+ - Product: Wire
+ Quantity: 1
+ Price: 0.32
+";
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Fields/Views/DefinitionTemplates/NumericFieldSettings.cshtml b/src/Orchard.Web/Modules/Orchard.Fields/Views/DefinitionTemplates/NumericFieldSettings.cshtml
index 45b536fae..ceaf210b2 100644
--- a/src/Orchard.Web/Modules/Orchard.Fields/Views/DefinitionTemplates/NumericFieldSettings.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.Fields/Views/DefinitionTemplates/NumericFieldSettings.cshtml
@@ -51,7 +51,7 @@
@Html.TextBoxFor(m => m.DefaultValue, new { @class = "text large tokenized ui-autocomplete-input" })
- @T("The default value for the field. It must be a number, and if not it will not shown. Make sure to set the Scale property if the value is not an integer. You can use tokens in this field. (optional)")
+ @T("The default value for the field. It must be a number, and if not it will not be shown. Make sure to set the Scale property if the value is not an integer. You can use tokens in this field. (optional)")
@Html.ValidationMessageFor(m => m.DefaultValue)
diff --git a/src/Orchard.Web/Modules/Orchard.ImportExport/Views/EditorTemplates/ImportActions/ExecuteRecipe.cshtml b/src/Orchard.Web/Modules/Orchard.ImportExport/Views/EditorTemplates/ImportActions/ExecuteRecipe.cshtml
index f4a8a4852..5033819e4 100644
--- a/src/Orchard.Web/Modules/Orchard.ImportExport/Views/EditorTemplates/ImportActions/ExecuteRecipe.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.ImportExport/Views/EditorTemplates/ImportActions/ExecuteRecipe.cshtml
@@ -29,7 +29,7 @@
}
- @Html.Hint(T("Select wether you want to import the selected recipe into the current site or if you want to do a full site reset first."))
+ @Html.Hint(T("Select whether you want to import the selected recipe into the current site or if you want to do a full site reset first."))
m.ResetSite), "true")" style="display: none;">
@T("This will delete your database tables. Please consider creating a backup first.")
@@ -63,4 +63,4 @@
stepIndex++;
}
-}
\ No newline at end of file
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs b/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs
index 8cab0b828..1333ef9c5 100644
--- a/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs
+++ b/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs
@@ -17,13 +17,14 @@ using Orchard.Layouts.Services;
using Orchard.Layouts.Shapes;
using Orchard.Layouts.ViewModels;
using Orchard.Localization;
+using Orchard.Services;
using Orchard.Themes.Services;
using Orchard.Tokens;
using Orchard.Utility.Extensions;
namespace Orchard.Layouts.Providers {
[OrchardFeature("Orchard.Layouts.Snippets")]
- public class SnippetElementHarvester : Component, IElementHarvester {
+ public class SnippetElementHarvester : IElementHarvester {
private const string SnippetShapeSuffix = "Snippet";
private readonly Work _shapeFactory;
private readonly Work _siteThemeService;
@@ -33,6 +34,7 @@ namespace Orchard.Layouts.Providers {
private readonly Work _currentThemeShapeBindingResolver;
private readonly Work _tokenizer;
private readonly IWorkContextAccessor _wca;
+ private readonly Work _yamlParser;
public SnippetElementHarvester(
IWorkContextAccessor workContextAccessor,
@@ -42,7 +44,8 @@ namespace Orchard.Layouts.Providers {
Work elementFactory,
Work shapeDisplay,
Work tokenizer,
- Work currentThemeShapeBindingResolver) {
+ Work currentThemeShapeBindingResolver,
+ Work yamlParser) {
_shapeFactory = shapeFactory;
_siteThemeService = siteThemeService;
@@ -51,6 +54,7 @@ namespace Orchard.Layouts.Providers {
_shapeDisplay = shapeDisplay;
_tokenizer = tokenizer;
_currentThemeShapeBindingResolver = currentThemeShapeBindingResolver;
+ _yamlParser = yamlParser;
_wca = workContextAccessor;
}
@@ -65,12 +69,13 @@ namespace Orchard.Layouts.Providers {
var shapeType = shapeDescriptor.Value.ShapeType;
var elementName = GetDisplayName(shapeDescriptor.Value.BindingSource);
var closureDescriptor = shapeDescriptor;
- yield return new ElementDescriptor(elementType, shapeType, T(elementName), T("An element that renders the {0} shape.", shapeType), snippetElement.Category) {
- Displaying = displayContext => Displaying(displayContext, closureDescriptor.Value),
+ var snippetDescriptor = ParseSnippetDescriptor(shapeDescriptor.Value.BindingSource);
+ yield return new ElementDescriptor(elementType, shapeType, new LocalizedString(elementName), new LocalizedString(String.Format("An element that renders the {0} shape.", shapeType)), snippetElement.Category) {
+ Displaying = displayContext => Displaying(displayContext, closureDescriptor.Value, snippetDescriptor),
ToolboxIcon = "\uf10c",
- EnableEditorDialog = HasSnippetFields(shapeDescriptor.Value),
- Editor = ctx => Editor(DescribeSnippet(shapeType, snippetElement), ctx),
- UpdateEditor = ctx => UpdateEditor(DescribeSnippet(shapeType, snippetElement), ctx)
+ EnableEditorDialog = snippetDescriptor != null || HasSnippetFields(shapeDescriptor.Value),
+ Editor = ctx => Editor(snippetDescriptor ?? DescribeSnippet(shapeType, snippetElement), ctx),
+ UpdateEditor = ctx => UpdateEditor(snippetDescriptor ?? DescribeSnippet(shapeType, snippetElement), ctx)
};
}
}
@@ -113,22 +118,53 @@ namespace Orchard.Layouts.Providers {
context.EditorResult.Add(snippetEditorShape);
}
- private void Displaying(ElementDisplayingContext context, ShapeDescriptor shapeDescriptor) {
+ private void Displaying(ElementDisplayingContext context, ShapeDescriptor shapeDescriptor, SnippetDescriptor snippetDescriptor) {
var shapeType = shapeDescriptor.ShapeType;
var shape = (dynamic)_shapeFactory.Value.Create(shapeType);
shape.Element = context.Element;
+ if (snippetDescriptor != null) {
+ foreach (var fieldDescriptor in snippetDescriptor.Fields) {
+ var value = context.Element.Data.Get(fieldDescriptor.Name);
+ shape.Properties[fieldDescriptor.Name] = value;
+ }
+ }
+
ElementShapes.AddTokenizers(shape, _tokenizer.Value);
context.ElementShape.Snippet = shape;
}
private string GetDisplayName(string bindingSource) {
- var fileName = Path.GetFileNameWithoutExtension(bindingSource);
+ var fileName = Path.GetFileNameWithoutExtension(bindingSource) ?? "";
var lastIndex = fileName.IndexOf(SnippetShapeSuffix, StringComparison.OrdinalIgnoreCase);
return fileName.Substring(0, lastIndex).CamelFriendly();
}
+ private SnippetDescriptor ParseSnippetDescriptor(string bindingSource) {
+ var physicalSourcePath = _wca.GetContext().HttpContext.Server.MapPath(bindingSource);
+ var paramsFileName = Path.Combine(Path.GetDirectoryName(physicalSourcePath) ?? "", Path.GetFileNameWithoutExtension(physicalSourcePath) + ".txt");
+
+ if (!File.Exists(paramsFileName))
+ return null;
+
+ var yaml = File.ReadAllText(paramsFileName);
+ var snippetConfig = _yamlParser.Value.Deserialize(yaml);
+ var fieldsConfig = snippetConfig.Fields != null ? snippetConfig.Fields.Children : new dynamic[0];
+ var descriptor = new SnippetDescriptor();
+
+ foreach (var fieldConfig in fieldsConfig) {
+ descriptor.Fields.Add(new SnippetFieldDescriptor {
+ Name = (string)fieldConfig.Name,
+ Type = (string)fieldConfig.Type,
+ DisplayName = new LocalizedString((string)fieldConfig.DisplayName),
+ Description = new LocalizedString((string)fieldConfig.Description)
+ });
+ }
+
+ return descriptor;
+ }
+
private SnippetDescriptor DescribeSnippet(string shapeType, Snippet element) {
var shape = (dynamic)_shapeFactory.Value.Create(shapeType);
shape.Element = element;
diff --git a/src/Orchard.Web/Modules/Orchard.Taxonomies/Views/EditorTemplates/Fields/TaxonomyField.cshtml b/src/Orchard.Web/Modules/Orchard.Taxonomies/Views/EditorTemplates/Fields/TaxonomyField.cshtml
index bdd8b2678..02d9d615b 100644
--- a/src/Orchard.Web/Modules/Orchard.Taxonomies/Views/EditorTemplates/Fields/TaxonomyField.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.Taxonomies/Views/EditorTemplates/Fields/TaxonomyField.cshtml
@@ -5,7 +5,7 @@
@{
Style.Include("admin-taxonomy.css");
- Script.Require("jQuery");
+ Script.Require("jQuery");
Script.Include("~/Themes/TheAdmin/scripts/admin.js").AtFoot();
Script.Include("admin-taxonomy-expando.js").AtFoot();
@@ -15,12 +15,12 @@
@Html.HiddenFor(m => m.TaxonomyId)
diff --git a/src/Orchard.Web/Themes/TheAdmin/Scripts/admin.js b/src/Orchard.Web/Themes/TheAdmin/Scripts/admin.js
index 53519f096..8fcae3ab7 100644
--- a/src/Orchard.Web/Themes/TheAdmin/Scripts/admin.js
+++ b/src/Orchard.Web/Themes/TheAdmin/Scripts/admin.js
@@ -81,6 +81,16 @@
$("input[type=checkbox]:not(:disabled)").prop('checked', $(this).prop("checked"))
});
+ //Prevent double-click on buttons of type "submit"
+ $("form button[type='submit'], form input[type='submit']").click(function (e) {
+ var form = $(this).closest("form")[0];
+ if (typeof(form.formSubmitted) != "undefined") {
+ e.preventDefault();
+ return;
+ }
+ form.formSubmitted = true;
+ });
+
// Handle keypress events in bulk action fieldsets that are part of a single form.
// This will make sure the expected action executes when pressing "enter" on a text field.
$("form .bulk-actions").on("keypress", "input[type='text']", function (e) {
diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config
index 2106f9674..cc10d9dab 100644
--- a/src/Orchard.Web/Web.config
+++ b/src/Orchard.Web/Web.config
@@ -225,7 +225,11 @@
-
+
+
+
+
+
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index 5cb38eb1d..be276337b 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -166,6 +166,14 @@
+
+ ..\packages\YamlDotNet.3.8.0\lib\net35\YamlDotNet.dll
+ True
+
+
+ ..\packages\YamlDotNet.Dynamic.3.2.3\lib\net40\YamlDotNet.Dynamic.dll
+ True
+
@@ -419,7 +427,9 @@
+
+
diff --git a/src/Orchard/Services/IYamlParser.cs b/src/Orchard/Services/IYamlParser.cs
new file mode 100644
index 000000000..cf9ea5f2c
--- /dev/null
+++ b/src/Orchard/Services/IYamlParser.cs
@@ -0,0 +1,21 @@
+namespace Orchard.Services {
+ ///
+ /// Provides methods to deserialize objects from YAML documents.
+ ///
+ public interface IYamlParser : IDependency {
+ ///
+ /// Deserializes a YAML document to a dynamic object.
+ ///
+ /// The YAML document to deserialize.
+ /// The deserialized object.
+ dynamic Deserialize(string yaml);
+
+ ///
+ /// Deserializes a YAML document to a specific object.
+ ///
+ /// The type of the object to deserialize.
+ /// The YAML document to deserialize.
+ /// The deserialized object.
+ T Deserialize(string yaml);
+ }
+}
diff --git a/src/Orchard/Services/YamlParser.cs b/src/Orchard/Services/YamlParser.cs
new file mode 100644
index 000000000..b783aad5c
--- /dev/null
+++ b/src/Orchard/Services/YamlParser.cs
@@ -0,0 +1,19 @@
+using System.IO;
+using YamlDotNet.Dynamic;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace Orchard.Services {
+ public class YamlParser : IYamlParser {
+ public dynamic Deserialize(string yaml) {
+ return new DynamicYaml(yaml);
+ }
+
+ public T Deserialize(string yaml) {
+ var deserializer = new Deserializer(namingConvention: new PascalCaseNamingConvention(), ignoreUnmatched: true);
+ using (var reader = new StringReader(yaml)) {
+ return deserializer.Deserialize(reader);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/packages.config b/src/Orchard/packages.config
index 2583dd2b6..f5f57f9e4 100644
--- a/src/Orchard/packages.config
+++ b/src/Orchard/packages.config
@@ -17,4 +17,6 @@
+
+
\ No newline at end of file