diff --git a/README.md b/README.md index 5cadb88..2aeffa5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A nested content property editor for Umbraco 7 that allows you to use Doc Types ### Installation -> *Note:* Nested Content has been developed against **Umbraco v7.1.4** and will support that version and above. +> *Note:* Nested Content has been developed against **Umbraco v7.1.5** and will support that version and above. Nested Content can be installed from either Our Umbraco or NuGet package repositories, or build manually from the source-code: diff --git a/appveyor.yml b/appveyor.yml index 57d07e6..bc29a33 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ # version format -version: 0.4.0.{build} +version: 0.5.0.{build} # UMBRACO_PACKAGE_PRERELEASE_SUFFIX if a rtm release build this should be blank, otherwise if empty will default to alpha # example UMBRACO_PACKAGE_PRERELEASE_SUFFIX=beta diff --git a/build/package.proj b/build/package.proj index c2de2cc..208216a 100644 --- a/build/package.proj +++ b/build/package.proj @@ -18,7 +18,7 @@ Our.Umbraco.NestedContent Nested Content - 7.1.4 + 7.1.5 Nested Content is a list editing property editor for Umbraco 7.1+ Matt Brailsford, Lee Kelleher https://github.com/umco/umbraco-nested-content/graphs/contributors diff --git a/src/Our.Umbraco.NestedContent/Bootstrap.cs b/src/Our.Umbraco.NestedContent/Bootstrap.cs index 311294e..2cb1ec0 100644 --- a/src/Our.Umbraco.NestedContent/Bootstrap.cs +++ b/src/Our.Umbraco.NestedContent/Bootstrap.cs @@ -1,7 +1,10 @@ -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Services; +using System; +using Newtonsoft.Json; +using Our.Umbraco.NestedContent.Helpers; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; namespace Our.Umbraco.NestedContent { @@ -9,16 +12,28 @@ public class Bootstrap : ApplicationEventHandler { protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - DataTypeService.Saved += ExpireCache; + CacheRefresherBase.CacheUpdated += DataTypeCacheRefresher_Updated; } - private void ExpireCache(IDataTypeService sender, SaveEventArgs e) + private void DataTypeCacheRefresher_Updated(DataTypeCacheRefresher sender, CacheRefresherEventArgs e) { - foreach (var dataType in e.SavedEntities) + if (e.MessageType == MessageType.RefreshByJson) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( - string.Concat("Our.Umbraco.NestedContent.GetPreValuesCollectionByDataTypeId_", dataType.Id)); + var payload = JsonConvert.DeserializeObject((string)e.MessageObject); + if (payload != null) + { + foreach (var item in payload) + { + NestedContentHelper.ClearCache(item.Id); + } + } } } + + private class JsonPayload + { + public Guid UniqueId { get; set; } + public int Id { get; set; } + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Converters/NestedContentValueConverter.cs b/src/Our.Umbraco.NestedContent/Converters/NestedContentValueConverter.cs index da536da..522283f 100644 --- a/src/Our.Umbraco.NestedContent/Converters/NestedContentValueConverter.cs +++ b/src/Our.Umbraco.NestedContent/Converters/NestedContentValueConverter.cs @@ -8,9 +8,7 @@ namespace Our.Umbraco.NestedContent.Converters { - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] - [PropertyValueType(typeof(IEnumerable))] - public class NestedContentValueConverter : PropertyValueConverterBase + public class NestedContentValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta { public override bool IsConverter(PublishedPropertyType propertyType) { @@ -30,5 +28,15 @@ public override object ConvertDataToSource(PublishedPropertyType propertyType, o return null; } + + public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) + { + return typeof (IEnumerable); + } + + public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) + { + return PropertyCacheLevel.Content; + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Converters/SingleNestedContentValueConverter.cs b/src/Our.Umbraco.NestedContent/Converters/SingleNestedContentValueConverter.cs index 9da1bd8..f55e9c9 100644 --- a/src/Our.Umbraco.NestedContent/Converters/SingleNestedContentValueConverter.cs +++ b/src/Our.Umbraco.NestedContent/Converters/SingleNestedContentValueConverter.cs @@ -7,9 +7,7 @@ namespace Our.Umbraco.NestedContent.Converters { - [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] - [PropertyValueType(typeof(IPublishedContent))] - public class SingleNestedContentValueConverter : PropertyValueConverterBase + public class SingleNestedContentValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta { public override bool IsConverter(PublishedPropertyType propertyType) { @@ -29,5 +27,15 @@ public override object ConvertDataToSource(PublishedPropertyType propertyType, o return null; } + + public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) + { + return typeof(IPublishedContent); + } + + public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) + { + return PropertyCacheLevel.Content; + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Extensions/ContentTypeServiceExtensions.cs b/src/Our.Umbraco.NestedContent/Extensions/ContentTypeServiceExtensions.cs deleted file mode 100644 index 735e651..0000000 --- a/src/Our.Umbraco.NestedContent/Extensions/ContentTypeServiceExtensions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.NestedContent.Extensions -{ - internal static class ContentTypeServiceExtensions - { - // implementation obsolete as of PR #4 - empty class left for future expansion - } -} \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Extensions/JsonExtensions.cs b/src/Our.Umbraco.NestedContent/Extensions/JsonExtensions.cs new file mode 100644 index 0000000..0e82afb --- /dev/null +++ b/src/Our.Umbraco.NestedContent/Extensions/JsonExtensions.cs @@ -0,0 +1,19 @@ +using System; +using Newtonsoft.Json.Linq; + +namespace Our.Umbraco.NestedContent.Extensions +{ + internal static class JsonExtensions + { + public static void Rename(this JToken token, string newName) + { + var parent = token.Parent; + + if (parent == null) + throw new InvalidOperationException("The parent is missing."); + + var newToken = new JProperty(newName, token); + parent.Replace(newToken); + } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Extensions/PreValueCollectionExtensions.cs b/src/Our.Umbraco.NestedContent/Extensions/PreValueCollectionExtensions.cs deleted file mode 100644 index d6a6de9..0000000 --- a/src/Our.Umbraco.NestedContent/Extensions/PreValueCollectionExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models; - -namespace Our.Umbraco.NestedContent.Extensions -{ - internal static class PreValueCollectionExtensions - { - public static IDictionary AsPreValueDictionary(this PreValueCollection preValue) - { - return preValue.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); - } - } -} \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Extensions/PublishedPropertyTypeExtensions.cs b/src/Our.Umbraco.NestedContent/Extensions/PublishedPropertyTypeExtensions.cs index 3315eb0..11a266f 100644 --- a/src/Our.Umbraco.NestedContent/Extensions/PublishedPropertyTypeExtensions.cs +++ b/src/Our.Umbraco.NestedContent/Extensions/PublishedPropertyTypeExtensions.cs @@ -27,7 +27,7 @@ public static bool IsSingleNestedContentProperty(this PublishedPropertyType publ } var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(publishedProperty.DataTypeId); - var preValueDictionary = preValueCollection.AsPreValueDictionary(); + var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); int minItems, maxItems; return preValueDictionary.ContainsKey("minItems") && @@ -46,7 +46,7 @@ public static object ConvertPropertyToNestedContent(this PublishedPropertyType p var processedValue = new List(); var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); - var preValueDictionary = preValueCollection.AsPreValueDictionary(); + var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); for (var i = 0; i < rawValue.Count; i++) { @@ -90,16 +90,26 @@ public static object ConvertPropertyToNestedContent(this PublishedPropertyType p } // Get the current request node we are embedded in - var pcr = UmbracoContext.Current.PublishedContentRequest; + var pcr = UmbracoContext.Current == null ? null : UmbracoContext.Current.PublishedContentRequest; var containerNode = pcr != null && pcr.HasPublishedContent ? pcr.PublishedContent : null; - processedValue.Add(new DetachedPublishedContent( + // Create the model based on our implementation of IPublishedContent + IPublishedContent content = new DetachedPublishedContent( nameObj == null ? null : nameObj.ToString(), publishedContentType, properties.ToArray(), containerNode, i, - preview)); + preview); + + if (PublishedContentModelFactoryResolver.HasCurrent && PublishedContentModelFactoryResolver.Current.HasValue) + { + // Let the current model factory create a typed model to wrap our model + content = PublishedContentModelFactoryResolver.Current.Factory.CreateModel(content); + } + + // Add the (typed) model as a result + processedValue.Add(content); } if (propertyType.IsSingleNestedContentProperty()) diff --git a/src/Our.Umbraco.NestedContent/Helpers/NestedContentHelper.cs b/src/Our.Umbraco.NestedContent/Helpers/NestedContentHelper.cs index c0f32ff..3eceeed 100644 --- a/src/Our.Umbraco.NestedContent/Helpers/NestedContentHelper.cs +++ b/src/Our.Umbraco.NestedContent/Helpers/NestedContentHelper.cs @@ -1,24 +1,35 @@ using System; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; using Our.Umbraco.NestedContent.Extensions; +using Our.Umbraco.NestedContent.Models; using Our.Umbraco.NestedContent.PropertyEditors; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Persistence; namespace Our.Umbraco.NestedContent.Helpers { internal static class NestedContentHelper { + private const string CacheKeyPrefix = "Our.Umbraco.NestedContent.GetPreValuesCollectionByDataTypeId_"; + public static PreValueCollection GetPreValuesCollectionByDataTypeId(int dtdId) { var preValueCollection = (PreValueCollection)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( - string.Concat("Our.Umbraco.NestedContent.GetPreValuesCollectionByDataTypeId_", dtdId), + string.Concat(CacheKeyPrefix, dtdId), () => ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtdId)); return preValueCollection; } + public static void ClearCache(int dtdId) + { + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( + string.Concat(CacheKeyPrefix, dtdId)); + } + public static string GetContentTypeAliasFromItem(JObject item) { var contentTypeAliasProperty = item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey]; @@ -60,7 +71,7 @@ public static void ConvertItemValueFromV011(JObject item, int dtdId, ref PreValu ConvertPreValueCollectionFromV011(preValues); // - get the content types prevalue as JArray - var preValuesAsDictionary = preValues.AsPreValueDictionary(); + var preValuesAsDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); if (!preValuesAsDictionary.ContainsKey(ContentTypesPreValueKey) || string.IsNullOrEmpty(preValuesAsDictionary[ContentTypesPreValueKey]) != false) { return; @@ -81,7 +92,7 @@ public static void ConvertPreValueCollectionFromV011(PreValueCollection preValue return; } - var persistedPreValuesAsDictionary = preValueCollection.AsPreValueDictionary(); + var persistedPreValuesAsDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); // do we have a "docTypeGuid" prevalue and no "contentTypes" prevalue? if (persistedPreValuesAsDictionary.ContainsKey("docTypeGuid") == false || persistedPreValuesAsDictionary.ContainsKey(ContentTypesPreValueKey)) @@ -121,5 +132,110 @@ private static string ContentTypesPreValueKey } #endregion + + public static void RemapDocTypeAlias(string oldAlias, string newAlias, Transaction transaction = null) + { + var db = ApplicationContext.Current.DatabaseContext.Database; + + // Update references in property data + // We do 2 very similar replace statements, but one is without spaces in the JSON, the other is with spaces + // as we can't guarantee what format it will actually get saved in + var sql1 = string.Format(@"UPDATE cmsPropertyData +SET dataNtext = CAST(REPLACE(REPLACE(CAST(dataNtext AS nvarchar(max)), '""ncContentTypeAlias"":""{0}""', '""ncContentTypeAlias"":""{1}""'), '""ncContentTypeAlias"": ""{0}""', '""ncContentTypeAlias"": ""{1}""') AS ntext) +WHERE dataNtext LIKE '%""ncContentTypeAlias"":""{0}""%' OR dataNtext LIKE '%""ncContentTypeAlias"": ""{0}""%'", oldAlias, newAlias); + + // Update references in prevalue + // We do 2 very similar replace statements, but one is without spaces in the JSON, the other is with spaces + // as we can't guarantee what format it will actually get saved in + var sql2 = string.Format(@"UPDATE cmsDataTypePreValues +SET [value] = CAST(REPLACE(REPLACE(CAST([value] AS nvarchar(max)), '""ncAlias"":""{0}""', '""ncAlias"":""{1}""'), '""ncAlias"": ""{0}""', '""ncAlias"": ""{1}""') AS ntext) +WHERE [value] LIKE '%""ncAlias"":""{0}""%' OR [value] LIKE '%""ncAlias"": ""{0}""%'", oldAlias, newAlias); + + if (transaction == null) + { + using (var tr = db.GetTransaction()) + { + db.Execute(sql1); + db.Execute(sql2); + tr.Complete(); + } + } + else + { + db.Execute(sql1); + db.Execute(sql2); + } + } + + public static void RemapPropertyAlias(string docTypeAlias, string oldAlias, string newAlias, Transaction transaction = null) + { + var db = ApplicationContext.Current.DatabaseContext.Database; + + // Update references in property data + // We have to do it in code because there could be nested JSON so + // we need to make sure it only replaces at the specific level only + Action doQuery = () => + { + var rows = GetPropertyDataRows(docTypeAlias); + foreach (var row in rows) + { + var tokens = row.Data.SelectTokens(string.Format("$..[?(@.ncContentTypeAlias == '{0}' && @.{1})]", docTypeAlias, oldAlias)).ToList(); + if (tokens.Any()) + { + foreach (var token in tokens) + { + token[oldAlias].Rename(newAlias); + } + db.Execute("UPDATE [cmsPropertyData] SET [dataNtext] = @0 WHERE [id] = @1", row.RawData, row.Id); + } + } + }; + + if (transaction == null) + { + using (var tr = db.GetTransaction()) + { + doQuery(); + tr.Complete(); + } + } + else + { + doQuery(); + } + } + + public static void RemapDocTypeTabAlias(string docTypeAlias, string oldAlias, string newAlias, Transaction transaction = null) + { + var db = ApplicationContext.Current.DatabaseContext.Database; + + // Update references in prevalue + // We do 2 very similar replace statements, but one is without spaces in the JSON, the other is with spaces + // as we can't guarantee what format it will actually get saved in + var sql1 = string.Format(@"UPDATE cmsDataTypePreValues +SET [value] = CAST(REPLACE(REPLACE(CAST([value] AS nvarchar(max)), '""ncTabAlias"":""{0}""', '""ncTabAlias"":""{1}""'), '""ncTabAlias"": ""{0}""', '""ncTabAlias"": ""{1}""') AS ntext) +WHERE [value] LIKE '%""ncAlias"":""{2}""%' OR [value] LIKE '%""ncAlias"": ""{2}""%'", oldAlias, newAlias, docTypeAlias); + + if (transaction == null) + { + using (var tr = db.GetTransaction()) + { + db.Execute(sql1); + tr.Complete(); + } + } + else + { + db.Execute(sql1); + } + } + + private static IEnumerable GetPropertyDataRows(string docTypeAlias) + { + var db = ApplicationContext.Current.DatabaseContext.Database; + return db.Query(string.Format( + @"SELECT [id], [dataNtext] as [rawdata] FROM cmsPropertyData WHERE dataNtext LIKE '%""ncContentTypeAlias"":""{0}""%' OR dataNtext LIKE '%""ncContentTypeAlias"": ""{0}""%'", + docTypeAlias)).ToList(); + } } } \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Models/JsonDbRow.cs b/src/Our.Umbraco.NestedContent/Models/JsonDbRow.cs new file mode 100644 index 0000000..f28286c --- /dev/null +++ b/src/Our.Umbraco.NestedContent/Models/JsonDbRow.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Our.Umbraco.NestedContent.Models +{ + /// + /// A utility class used to help modify JSON data from the Umbraco property data table + /// + internal class JsonDbRow + { + public int Id { get; set; } + + public string RawData { get; set; } + + public JToken Data + { + get + { + return (JToken)JsonConvert.DeserializeObject(RawData); + } + set + { + RawData = JsonConvert.SerializeObject(value); + } + } + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Our.Umbraco.NestedContent.csproj b/src/Our.Umbraco.NestedContent/Our.Umbraco.NestedContent.csproj index 3006c60..42642f7 100644 --- a/src/Our.Umbraco.NestedContent/Our.Umbraco.NestedContent.csproj +++ b/src/Our.Umbraco.NestedContent/Our.Umbraco.NestedContent.csproj @@ -32,210 +32,65 @@ 4 - - ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.dll + + ..\packages\UmbracoCms.Core.7.1.5\lib\businesslogic.dll False - - ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll + + ..\packages\UmbracoCms.Core.7.1.5\lib\cms.dll False - - ..\packages\UmbracoCms.Core.7.1.4\lib\businesslogic.dll + + ..\packages\UmbracoCms.Core.7.1.5\lib\interfaces.dll False - - ..\packages\ClientDependency.1.8.2.1\lib\net45\ClientDependency.Core.dll - False - - - ..\packages\ClientDependency-Mvc.1.7.0.4\lib\ClientDependency.Core.Mvc.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\cms.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\controls.dll - False - - - ..\packages\xmlrpcnet.2.5.0\lib\net20\CookComputing.XmlRpcV2.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\Examine.dll - False - - - ..\packages\HtmlAgilityPack.1.4.6\lib\Net45\HtmlAgilityPack.dll - False - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - False - - - ..\packages\ImageProcessor.1.9.0.0\lib\ImageProcessor.dll - False - - - ..\packages\ImageProcessor.Web.3.2.3.0\lib\net45\ImageProcessor.Web.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\interfaces.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\log4net.dll - False - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\Microsoft.ApplicationBlocks.Data.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\Microsoft.Web.Helpers.dll - False - - - False - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.0\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll + + ..\packages\UmbracoCms.Core.7.1.5\lib\log4net.dll False ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll False - - ..\packages\MySql.Data.6.6.5\lib\net40\MySql.Data.dll - False - - - ..\packages\Newtonsoft.Json.6.0.2\lib\net45\Newtonsoft.Json.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\SQLCE4Umbraco.dll + + ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll False - - False - ..\packages\UmbracoCms.Core.7.1.4\lib\System.Data.SqlServerCe.dll - - - False - ..\packages\UmbracoCms.Core.7.1.4\lib\System.Data.SqlServerCe.Entity.dll - - - False - ..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll - + - - False - ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll - + ..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll False - ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll - - - False - ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll - False - ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll - False - - - False - - - False - ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll - - - False - ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Deployment.dll - - + ..\packages\Microsoft.AspNet.Mvc.4.0.30506.0\lib\net40\System.Web.Mvc.dll False - ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Razor.dll - - - - ..\packages\UmbracoCms.Core.7.1.4\lib\TidyNet.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\umbraco.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\Umbraco.Core.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\umbraco.DataLayer.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\umbraco.editorControls.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\umbraco.MacroEngines.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\umbraco.providers.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\Umbraco.Web.UI.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\umbraco.XmlSerializers.dll - False - - - ..\packages\UmbracoCms.Core.7.1.4\lib\UmbracoExamine.dll + + ..\packages\UmbracoCms.Core.7.1.5\lib\umbraco.dll False - - ..\packages\UmbracoCms.Core.7.1.4\lib\UrlRewritingNet.UrlRewriter.dll + + ..\packages\UmbracoCms.Core.7.1.5\lib\Umbraco.Core.dll False + - - + @@ -257,13 +112,8 @@ - IF %25ComputerName%25 == MBP13-PC-BC ( - IF NOT "$(SolutionDir)" == "*Undefined*" ( - xcopy /s /y "$(TargetPath)" "C:\Users\Matt\Work\Sandbox\Umbraco\UmbracoUHangoutDemo\bin" - xcopy /s /y "$(TargetDir)$(ProjectName).pdb" "C:\Users\Matt\Work\Sandbox\Umbraco\UmbracoUHangoutDemo\bin" - xcopy /s /y "$(ProjectDir)Web\UI\*.*" "C:\Users\Matt\Work\Sandbox\Umbraco\UmbracoUHangoutDemo" - ) -) + + diff --git a/src/Our.Umbraco.NestedContent/Properties/VersionInfo.cs b/src/Our.Umbraco.NestedContent/Properties/VersionInfo.cs index 90d688c..3144bd2 100644 --- a/src/Our.Umbraco.NestedContent/Properties/VersionInfo.cs +++ b/src/Our.Umbraco.NestedContent/Properties/VersionInfo.cs @@ -13,7 +13,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -[assembly: AssemblyVersion("0.3.*")] -[assembly: AssemblyInformationalVersion("0.3.0-develop")] +[assembly: AssemblyVersion("0.5.*")] +[assembly: AssemblyInformationalVersion("0.5.0-develop")] diff --git a/src/Our.Umbraco.NestedContent/PropertyEditors/NestedContentPropertyEditor.cs b/src/Our.Umbraco.NestedContent/PropertyEditors/NestedContentPropertyEditor.cs index 160a380..88fdb5c 100644 --- a/src/Our.Umbraco.NestedContent/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Our.Umbraco.NestedContent/PropertyEditors/NestedContentPropertyEditor.cs @@ -5,7 +5,6 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Our.Umbraco.NestedContent.Extensions; using Our.Umbraco.NestedContent.Helpers; using Umbraco.Core; using Umbraco.Core.Models; @@ -107,7 +106,7 @@ public override void ConfigureForDisplay(PreValueCollection preValues) { base.ConfigureForDisplay(preValues); - var asDictionary = preValues.AsPreValueDictionary(); + var asDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value); if (asDictionary.ContainsKey("hideLabel")) { var boolAttempt = asDictionary["hideLabel"].TryConvertTo(); @@ -170,8 +169,7 @@ public override string ConvertDbToString(Property property, PropertyType propert var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); // Get the editor to do it's conversion, and store it back - propValues[propKey] = propEditor.ValueEditor.ConvertDbToString(prop, propType, - ApplicationContext.Current.Services.DataTypeService); + propValues[propKey] = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService); } catch (InvalidOperationException) { @@ -239,15 +237,14 @@ public override object ConvertDbToEditor(Property property, PropertyType propert { try { - // Create a fake property using the property abd stored value + // Create a fake property using the property and stored value var prop = new Property(propType, propValues[propKey] == null ? null : propValues[propKey].ToString()); // Lookup the property editor var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias); // Get the editor to do it's conversion - var newValue = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, - ApplicationContext.Current.Services.DataTypeService); + var newValue = propEditor.ValueEditor.ConvertDbToEditor(prop, propType, dataTypeService); // Store the value back propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue); diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Css/nestedcontent.css b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Css/nestedcontent.css index 809119f..4f24fcf 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Css/nestedcontent.css +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Css/nestedcontent.css @@ -1,9 +1,25 @@ -.nested-content +.nested-content { text-align: center; } -.nested-content__item +.nested-content--not-supported +{ + opacity: 0.3; + pointer-events: none; +} + +.nested-content-overlay +{ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; +} + +.nested-content__item { position: relative; text-align: left; @@ -18,7 +34,7 @@ background: #f8f8f8; } -.nested-content__item.ui-sortable-placeholder +.nested-content__item.ui-sortable-placeholder { background: #f8f8f8; border: 1px dashed #d9d9d9; @@ -37,18 +53,18 @@ margin: 0; } -.nested-content__header-bar +.nested-content__header-bar { - padding: 15px 20px; + padding: 15px 20px; border-bottom: 1px dashed #e0e0e0; text-align: right; cursor: pointer; background-color: white; - -moz-user-select: none; - -khtml-user-select: none; - -webkit-user-select: none; - -o-user-select: none; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + -o-user-select: none; } .nested-content__heading @@ -57,14 +73,14 @@ line-height: 20px; } -.nested-content__heading i +.nested-content__heading i { vertical-align: text-top; color: #999; /* same icon color as the icons in the item type picker */ margin-right: 10px; } -.nested-content__icons +.nested-content__icons { margin: -6px 0; opacity: 0; @@ -116,19 +132,19 @@ color: white; } -.nested-content__icon--disabled +.nested-content__icon--disabled { opacity: 0.3; } -.nested-content__footer-bar +.nested-content__footer-bar { text-align: center; padding-top: 20px; } -.nested-content__content +.nested-content__content { border-bottom: 1px dashed #e0e0e0; } @@ -142,7 +158,7 @@ display: none !important; } -.nested-content__help-text +.nested-content__help-text { display: inline-block; padding: 10px 20px 10px 20px; @@ -171,7 +187,7 @@ margin-left: 10px; } -.form-horizontal .nested-content--narrow .controls-row +.form-horizontal .nested-content--narrow .controls-row { margin-left: 40% !important; } @@ -185,3 +201,8 @@ .form-horizontal .nested-content--narrow .controls-row .umb-dropdown { width: 99%; } + +.usky-grid.nested-content__node-type-picker .cell-tools-menu { + position: relative; + transform: translate(-50%, -25%); +} diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.controllers.js b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.controllers.js index 05a07b1..29abca9 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.controllers.js +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.controllers.js @@ -16,17 +16,6 @@ ); } - $scope.selectedDocTypeTabs = function (cfg) { - var dt = _.find($scope.model.docTypes, function (itm) { - return itm.alias.toLowerCase() == cfg.ncAlias.toLowerCase(); - }); - var tabs = dt ? dt.tabs : []; - if (!_.contains(tabs, cfg.ncTabAlias)) { - cfg.ncTabAlias = tabs[0]; - } - return tabs; - } - $scope.remove = function (index) { $scope.model.value.splice(index, 1); } @@ -37,8 +26,15 @@ handle: ".icon-navigation" }; + $scope.docTypeTabs = {}; + ncResources.getContentTypes().then(function (docTypes) { $scope.model.docTypes = docTypes; + + // Populate document type tab dictionary + docTypes.forEach(function (value) { + $scope.docTypeTabs[value.alias] = value.tabs; + }); }); if (!$scope.model.value) { @@ -56,9 +52,9 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest "$timeout", "contentResource", "localizationService", - "Our.Umbraco.NestedContent.Resources.NestedContentResources", + "iconHelper", - function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, ncResources) { + function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) { //$scope.model.config.contentTypes; //$scope.model.config.minItems; @@ -113,12 +109,20 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest style: {} }; + // helper to force the current form into the dirty state + $scope.setDirty = function () { + if ($scope.propertyForm) { + $scope.propertyForm.$setDirty(); + } + }; + $scope.addNode = function (alias) { var scaffold = $scope.getScaffold(alias); var newNode = initNode(scaffold, null); $scope.currentNode = newNode; + $scope.setDirty(); $scope.closeNodeTypePicker(); }; @@ -131,15 +135,10 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest // this could be used for future limiting on node types $scope.overlayMenu.scaffolds = []; _.each($scope.scaffolds, function (scaffold) { - var icon = scaffold.icon; - // workaround for when no icon is chosen for a doctype - if (icon == ".sprTreeFolder") { - icon = "icon-folder"; - } $scope.overlayMenu.scaffolds.push({ alias: scaffold.contentTypeAlias, name: scaffold.contentTypeName, - icon: icon + icon: iconHelper.convertFromLegacyIcon(scaffold.icon) }); }); @@ -153,24 +152,7 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest return; } - // Position off screen till we are visible and can calculate offset - $scope.overlayMenu.style.top = -1000; - $scope.overlayMenu.style.left = -1000; - $scope.overlayMenu.show = true; - - $timeout(function () { - - var wrapper = $("#contentwrapper"); - var el = $("#nested-content--" + $scope.model.id + " .nested-content__node-type-picker .cell-tools-menu"); - - var offset = el.offsetRelative("#contentwrapper"); - - $scope.overlayMenu.style.top = (Math.round(wrapper.height() / 2) + offset.top) - Math.round(el.height() / 2); - $scope.overlayMenu.style.left = (Math.round(wrapper.width() / 2) + offset.left) - Math.round(el.width() / 2); - - }); - }; $scope.closeNodeTypePicker = function () { @@ -190,10 +172,12 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) { if (confirm("Are you sure you want to delete this item?")) { $scope.nodes.splice(idx, 1); + $scope.setDirty(); updateModel(); } } else { $scope.nodes.splice(idx, 1); + $scope.setDirty(); updateModel(); } } @@ -236,7 +220,7 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest $scope.getIcon = function (idx) { var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias); - return scaffold && scaffold.icon && scaffold.icon !== ".sprTreeFolder" ? scaffold.icon : "icon-folder"; + return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } $scope.sortableOptions = { @@ -253,6 +237,9 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest $scope.sorting = true; }); }, + update: function (ev, ui) { + $scope.setDirty(); + }, stop: function (ev, ui) { $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () { tinymce.execCommand('mceAddEditor', true, $(this).attr('id')); @@ -277,6 +264,17 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest }); } + var notSupported = [ + "Umbraco.CheckBoxList", + "Umbraco.DropDownMultiple", + "Umbraco.MacroContainer", + "Umbraco.RadioButtonList", + "Umbraco.MultipleTextstring", + "Umbraco.Tags", + "Umbraco.UploadField", + "Umbraco.ImageCropper" + ]; + // Initialize var scaffoldsLoaded = 0; $scope.scaffolds = []; @@ -289,6 +287,14 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest scaffold.tabs = []; if (tab) { scaffold.tabs.push(tab); + + angular.forEach(tab.properties, + function (property) { + if (_.find(notSupported, function (x) { return x === property.editor; })) { + property.notSupported = true; + property.notSupportedMessage = "Property " + property.label + " uses editor " + property.editor + " which is not supported by Nested Content."; + } + }); } // Store the scaffold object @@ -347,7 +353,7 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest var initNode = function (scaffold, item) { var node = angular.copy(scaffold); - node.id = guid(); + node.id = item && item.id ? item.id : UUID.generate(); node.ncContentTypeAlias = scaffold.contentTypeAlias; for (var t = 0; t < node.tabs.length; t++) { @@ -416,57 +422,21 @@ angular.module("umbraco").controller("Our.Umbraco.NestedContent.Controllers.Nest unsubscribe(); }); - var guid = function () { - function _p8(s) { - var p = (Math.random().toString(16) + "000000000").substr(2, 8); - return s ? "-" + p.substr(0, 4) + "-" + p.substr(4, 4) : p; + var UUID = (function () { + var self = {}; + var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); } + self.generate = function () { + var d0 = Math.random() * 0xffffffff | 0; + var d1 = Math.random() * 0xffffffff | 0; + var d2 = Math.random() * 0xffffffff | 0; + var d3 = Math.random() * 0xffffffff | 0; + return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' + + lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' + + lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] + + lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff]; } - return _p8() + _p8(true) + _p8(true) + _p8(); - }; + return self; + })(); } ]); - -// offsetRelative (or, if you prefer, positionRelative) -(function ($) { - - $.fn.offsetRelative = function (ancestor) { - var positionedAncestor = $(ancestor); - var object = $(this); - - var relativeOffset = { left: 0, top: 0 }; - - var leftSpacing = parseInt(object.css("margin-left")); - leftSpacing += parseInt(object.css("border-left-width")); - - var topSpacing = parseInt(object.css("margin-top")); - topSpacing += parseInt(object.css("border-top-width")); - - relativeOffset.left -= leftSpacing; - relativeOffset.top -= topSpacing; - - var offsetParent = object.offsetParent(); - - while (offsetParent[0] !== positionedAncestor[0] && !offsetParent.is('html')) { - var offsetParentPosition = offsetParent.position(); - - var offsetParentPositionLeft = offsetParentPosition.left; - var offsetParentPositionTop = offsetParentPosition.top; - - relativeOffset.top -= offsetParentPositionTop; - relativeOffset.left -= offsetParentPositionLeft; - - leftSpacing = parseInt(offsetParent.css("margin-left")); - leftSpacing += parseInt(offsetParent.css("border-left-width")); - topSpacing = parseInt(offsetParent.css("margin-top")); - topSpacing += parseInt(offsetParent.css("border-top-width")); - - relativeOffset.left -= leftSpacing; - relativeOffset.top -= topSpacing; - - offsetParent = offsetParent.offsetParent(); - } - return relativeOffset; - }; - -}(jQuery)); \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.directives.js b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.directives.js index 01e3794..92518fc 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.directives.js +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.directives.js @@ -16,7 +16,7 @@ if ($scope.tabAlias) { angular.forEach($scope.model.tabs, function (tab) { - if (tab.alias.toLowerCase() == $scope.tabAlias.toLowerCase()) { + if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) { selectedTab = tab; return; } @@ -34,7 +34,7 @@ // Sync the values back angular.forEach($scope.ngModel.tabs, function (tab) { - if (tab.alias.toLowerCase() == selectedTab.alias.toLowerCase()) { + if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) { var localPropsMap = selectedTab.properties.reduce(function (map, obj) { map[obj.alias] = obj; @@ -55,7 +55,7 @@ $scope.$on('$destroy', function () { unsubscribe(); }); - } + }; return { restrict: "E", diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.filters.js b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.filters.js index cecd1fa..c0d6674 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.filters.js +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Js/nestedcontent.filters.js @@ -5,21 +5,21 @@ var ncNodeNameCache = { id: "", keys: {} -} +}; angular.module("umbraco.filters").filter("ncNodeName", function (editorState, entityResource) { return function (input) { // Check we have a value at all - if (input == "" || input.toString() == "0") + if (input === "" || input.toString() === "0") return ""; var currentNode = editorState.getCurrent(); // Ensure a unique cache per editor instance var key = "ncNodeName_" + currentNode.key; - if (ncNodeNameCache.id != key) { + if (ncNodeNameCache.id !== key) { ncNodeNameCache.id = key; ncNodeNameCache.keys = {}; } @@ -41,6 +41,6 @@ angular.module("umbraco.filters").filter("ncNodeName", function (editorState, en // Return the current value for now return ncNodeNameCache.keys[input]; - } + }; }); \ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.doctypepicker.html b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.doctypepicker.html index 3572a5e..a09e572 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.doctypepicker.html +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.doctypepicker.html @@ -5,13 +5,13 @@ - Document Type + Document Type - Tab + Tab - Name Template + Template @@ -27,31 +27,35 @@ - Remove + + Delete +
- Add + + Add +

- Tab:
- Select the tab who's properties should be displayed. If left blank, the first tab on the doc type will be used. + Tab:
+ Select the tab who's properties should be displayed. If left blank, the first tab on the doc type will be used.

- Name template:
+ Template:
Enter an angular expression to evaluate against each item for its name. Use {{$index}} to display the item index

diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.editor.html b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.editor.html index b3d338f..29d8c88 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.editor.html +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/Views/nestedcontent.editor.html @@ -1,9 +1,13 @@ 
+
- - + + +
+ +

{{property.notSupportedMessage}}

+ +
\ No newline at end of file diff --git a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/package.manifest b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/package.manifest index 62fa763..b08fc7d 100644 --- a/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/package.manifest +++ b/src/Our.Umbraco.NestedContent/Web/UI/App_Plugins/NestedContent/package.manifest @@ -1,9 +1,9 @@ { "javascript" : [ - '~/App_Plugins/NestedContent/Js/nestedcontent.filters.js', - '~/App_Plugins/NestedContent/Js/nestedcontent.resources.js', - '~/App_Plugins/NestedContent/Js/nestedcontent.directives.js', - '~/App_Plugins/NestedContent/Js/nestedcontent.controllers.js' + "~/App_Plugins/NestedContent/Js/nestedcontent.filters.js", + "~/App_Plugins/NestedContent/Js/nestedcontent.resources.js", + "~/App_Plugins/NestedContent/Js/nestedcontent.directives.js", + "~/App_Plugins/NestedContent/Js/nestedcontent.controllers.js" ], "css" : [ "~/App_Plugins/NestedContent/Css/nestedcontent.css" diff --git a/src/Our.Umbraco.NestedContent/packages.config b/src/Our.Umbraco.NestedContent/packages.config index d0f2fca..2a6f062 100644 --- a/src/Our.Umbraco.NestedContent/packages.config +++ b/src/Our.Umbraco.NestedContent/packages.config @@ -3,23 +3,25 @@ + - - + + - - + + - - + + + - + - + \ No newline at end of file