Merge remote-tracking branch 'origin/temp-u4-8604-1' into dev-v7.7
This commit is contained in:
@@ -437,6 +437,11 @@ namespace Umbraco.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const string EmailAddressAlias = "Umbraco.EmailAddress";
|
public const string EmailAddressAlias = "Umbraco.EmailAddress";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Alias for the nested content property editor.
|
||||||
|
/// </summary>
|
||||||
|
public const string NestedContentAlias = "Umbraco.NestedContent";
|
||||||
|
|
||||||
public static class PreValueKeys
|
public static class PreValueKeys
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
if (_index.HasValue) return _index.Value;
|
if (_index.HasValue) return _index.Value;
|
||||||
|
|
||||||
// slow -- and don't cache, not in a set
|
// slow -- and don't cache, not in a set
|
||||||
if (_contentSet == null) return Content.GetIndex();
|
if (_contentSet == null) return WrappedContentInternal.GetIndex();
|
||||||
|
|
||||||
// slow -- but cache for next time
|
// slow -- but cache for next time
|
||||||
var index = _contentSet.FindIndex(x => x.Id == Id);
|
var index = _contentSet.FindIndex(x => x.Id == Id);
|
||||||
@@ -147,7 +147,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
|
|
||||||
public override IEnumerable<IPublishedContent> ContentSet
|
public override IEnumerable<IPublishedContent> ContentSet
|
||||||
{
|
{
|
||||||
get { return _contentSet ?? Content.ContentSet; }
|
get { return _contentSet ?? WrappedContentInternal.ContentSet; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -161,8 +161,8 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _properties == null
|
return _properties == null
|
||||||
? Content.Properties
|
? WrappedContentInternal.Properties
|
||||||
: Content.Properties.Union(_properties).ToList();
|
: WrappedContentInternal.Properties.Union(_properties).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,15 +175,15 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
var property = _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias));
|
var property = _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias));
|
||||||
if (property != null) return property.HasValue ? property.Value : null;
|
if (property != null) return property.HasValue ? property.Value : null;
|
||||||
}
|
}
|
||||||
return Content[alias];
|
return WrappedContentInternal[alias];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IPublishedProperty GetProperty(string alias)
|
public override IPublishedProperty GetProperty(string alias)
|
||||||
{
|
{
|
||||||
return _properties == null
|
return _properties == null
|
||||||
? Content.GetProperty(alias)
|
? WrappedContentInternal.GetProperty(alias)
|
||||||
: _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias)) ?? Content.GetProperty(alias);
|
: _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias)) ?? WrappedContentInternal.GetProperty(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
: base(content)
|
: base(content)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } }
|
public Guid Key { get { return ((IPublishedContentWithKey) WrappedContentInternal).Key; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
: base (content)
|
: base (content)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } }
|
public Guid Key { get { return ((IPublishedContentWithKey) WrappedContentInternal).Key; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
: base(content)
|
: base(content)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public virtual Guid Key { get { return ((IPublishedContentWithKey) Content).Key; } }
|
public virtual Guid Key { get { return ((IPublishedContentWithKey) WrappedContentInternal).Key; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PublishedContentWrapped : IPublishedContent
|
public abstract class PublishedContentWrapped : IPublishedContent
|
||||||
{
|
{
|
||||||
protected readonly IPublishedContent Content;
|
protected readonly IPublishedContent WrappedContentInternal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize a new instance of the <see cref="PublishedContentWrapped"/> class
|
/// Initialize a new instance of the <see cref="PublishedContentWrapped"/> class
|
||||||
@@ -36,7 +36,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
/// <param name="content">The content to wrap and extend.</param>
|
/// <param name="content">The content to wrap and extend.</param>
|
||||||
protected PublishedContentWrapped(IPublishedContent content)
|
protected PublishedContentWrapped(IPublishedContent content)
|
||||||
{
|
{
|
||||||
Content = content;
|
WrappedContentInternal = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,21 +45,21 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
/// <returns>The wrapped content, that was passed as an argument to the constructor.</returns>
|
/// <returns>The wrapped content, that was passed as an argument to the constructor.</returns>
|
||||||
public IPublishedContent Unwrap()
|
public IPublishedContent Unwrap()
|
||||||
{
|
{
|
||||||
return Content;
|
return WrappedContentInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region ContentSet
|
#region ContentSet
|
||||||
|
|
||||||
public virtual IEnumerable<IPublishedContent> ContentSet
|
public virtual IEnumerable<IPublishedContent> ContentSet
|
||||||
{
|
{
|
||||||
get { return Content.ContentSet; }
|
get { return WrappedContentInternal.ContentSet; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ContentType
|
#region ContentType
|
||||||
|
|
||||||
public virtual PublishedContentType ContentType { get { return Content.ContentType; } }
|
public virtual PublishedContentType ContentType { get { return WrappedContentInternal.ContentType; } }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -67,102 +67,102 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
|
|
||||||
public virtual int Id
|
public virtual int Id
|
||||||
{
|
{
|
||||||
get { return Content.Id; }
|
get { return WrappedContentInternal.Id; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int TemplateId
|
public virtual int TemplateId
|
||||||
{
|
{
|
||||||
get { return Content.TemplateId; }
|
get { return WrappedContentInternal.TemplateId; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int SortOrder
|
public virtual int SortOrder
|
||||||
{
|
{
|
||||||
get { return Content.SortOrder; }
|
get { return WrappedContentInternal.SortOrder; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string Name
|
public virtual string Name
|
||||||
{
|
{
|
||||||
get { return Content.Name; }
|
get { return WrappedContentInternal.Name; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string UrlName
|
public virtual string UrlName
|
||||||
{
|
{
|
||||||
get { return Content.UrlName; }
|
get { return WrappedContentInternal.UrlName; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string DocumentTypeAlias
|
public virtual string DocumentTypeAlias
|
||||||
{
|
{
|
||||||
get { return Content.DocumentTypeAlias; }
|
get { return WrappedContentInternal.DocumentTypeAlias; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int DocumentTypeId
|
public virtual int DocumentTypeId
|
||||||
{
|
{
|
||||||
get { return Content.DocumentTypeId; }
|
get { return WrappedContentInternal.DocumentTypeId; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string WriterName
|
public virtual string WriterName
|
||||||
{
|
{
|
||||||
get { return Content.WriterName; }
|
get { return WrappedContentInternal.WriterName; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string CreatorName
|
public virtual string CreatorName
|
||||||
{
|
{
|
||||||
get { return Content.CreatorName; }
|
get { return WrappedContentInternal.CreatorName; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int WriterId
|
public virtual int WriterId
|
||||||
{
|
{
|
||||||
get { return Content.WriterId; }
|
get { return WrappedContentInternal.WriterId; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int CreatorId
|
public virtual int CreatorId
|
||||||
{
|
{
|
||||||
get { return Content.CreatorId; }
|
get { return WrappedContentInternal.CreatorId; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string Path
|
public virtual string Path
|
||||||
{
|
{
|
||||||
get { return Content.Path; }
|
get { return WrappedContentInternal.Path; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual DateTime CreateDate
|
public virtual DateTime CreateDate
|
||||||
{
|
{
|
||||||
get { return Content.CreateDate; }
|
get { return WrappedContentInternal.CreateDate; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual DateTime UpdateDate
|
public virtual DateTime UpdateDate
|
||||||
{
|
{
|
||||||
get { return Content.UpdateDate; }
|
get { return WrappedContentInternal.UpdateDate; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Guid Version
|
public virtual Guid Version
|
||||||
{
|
{
|
||||||
get { return Content.Version; }
|
get { return WrappedContentInternal.Version; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int Level
|
public virtual int Level
|
||||||
{
|
{
|
||||||
get { return Content.Level; }
|
get { return WrappedContentInternal.Level; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string Url
|
public virtual string Url
|
||||||
{
|
{
|
||||||
get { return Content.Url; }
|
get { return WrappedContentInternal.Url; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual PublishedItemType ItemType
|
public virtual PublishedItemType ItemType
|
||||||
{
|
{
|
||||||
get { return Content.ItemType; }
|
get { return WrappedContentInternal.ItemType; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool IsDraft
|
public virtual bool IsDraft
|
||||||
{
|
{
|
||||||
get { return Content.IsDraft; }
|
get { return WrappedContentInternal.IsDraft; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int GetIndex()
|
public virtual int GetIndex()
|
||||||
{
|
{
|
||||||
return Content.GetIndex();
|
return WrappedContentInternal.GetIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -171,12 +171,12 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
|
|
||||||
public virtual IPublishedContent Parent
|
public virtual IPublishedContent Parent
|
||||||
{
|
{
|
||||||
get { return Content.Parent; }
|
get { return WrappedContentInternal.Parent; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<IPublishedContent> Children
|
public virtual IEnumerable<IPublishedContent> Children
|
||||||
{
|
{
|
||||||
get { return Content.Children; }
|
get { return WrappedContentInternal.Children; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -185,22 +185,22 @@ namespace Umbraco.Core.Models.PublishedContent
|
|||||||
|
|
||||||
public virtual ICollection<IPublishedProperty> Properties
|
public virtual ICollection<IPublishedProperty> Properties
|
||||||
{
|
{
|
||||||
get { return Content.Properties; }
|
get { return WrappedContentInternal.Properties; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual object this[string alias]
|
public virtual object this[string alias]
|
||||||
{
|
{
|
||||||
get { return Content[alias]; }
|
get { return WrappedContentInternal[alias]; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IPublishedProperty GetProperty(string alias)
|
public virtual IPublishedProperty GetProperty(string alias)
|
||||||
{
|
{
|
||||||
return Content.GetProperty(alias);
|
return WrappedContentInternal.GetProperty(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IPublishedProperty GetProperty(string alias, bool recurse)
|
public virtual IPublishedProperty GetProperty(string alias, bool recurse)
|
||||||
{
|
{
|
||||||
return Content.GetProperty(alias, recurse);
|
return WrappedContentInternal.GetProperty(alias, recurse);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -42,7 +42,16 @@ namespace Umbraco.Core.PropertyEditors
|
|||||||
internal PropertyEditorResolver(IServiceProvider serviceProvider, ILogger logger, Func<IEnumerable<Type>> typeListProducerList, ManifestBuilder builder)
|
internal PropertyEditorResolver(IServiceProvider serviceProvider, ILogger logger, Func<IEnumerable<Type>> typeListProducerList, ManifestBuilder builder)
|
||||||
: base(serviceProvider, logger, typeListProducerList, ObjectLifetimeScope.Application)
|
: base(serviceProvider, logger, typeListProducerList, ObjectLifetimeScope.Application)
|
||||||
{
|
{
|
||||||
_unioned = new Lazy<List<PropertyEditor>>(() => Values.Union(builder.PropertyEditors).ToList());
|
_unioned = new Lazy<List<PropertyEditor>>(() => SanitizeNames(Values.Union(builder.PropertyEditors).ToList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PropertyEditor> SanitizeNames(List<PropertyEditor> editors)
|
||||||
|
{
|
||||||
|
var nestedContentEditorFromPackage = editors.FirstOrDefault(x => x.Alias == "Our.Umbraco.NestedContent");
|
||||||
|
if (nestedContentEditorFromPackage != null)
|
||||||
|
nestedContentEditorFromPackage.Name = "(Obsolete) " + nestedContentEditorFromPackage.Name;
|
||||||
|
return editors;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Lazy<List<PropertyEditor>> _unioned;
|
private readonly Lazy<List<PropertyEditor>> _unioned;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
|
|
||||||
protected T Resolve<T>(string propertyTypeAlias)
|
protected T Resolve<T>(string propertyTypeAlias)
|
||||||
{
|
{
|
||||||
return Content.GetPropertyValue<T>(propertyTypeAlias);
|
return WrappedContentInternal.GetPropertyValue<T>(propertyTypeAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected T Resolve<T>(MethodBase methodBase, T ifCannotConvert)
|
protected T Resolve<T>(MethodBase methodBase, T ifCannotConvert)
|
||||||
@@ -59,7 +59,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
|
|
||||||
protected T Resolve<T>(string propertyTypeAlias, T ifCannotConvert)
|
protected T Resolve<T>(string propertyTypeAlias, T ifCannotConvert)
|
||||||
{
|
{
|
||||||
return Content.GetPropertyValue<T>(propertyTypeAlias, false, ifCannotConvert);
|
return WrappedContentInternal.GetPropertyValue<T>(propertyTypeAlias, false, ifCannotConvert);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected T Resolve<T>(MethodBase methodBase, bool recursive, T ifCannotConvert)
|
protected T Resolve<T>(MethodBase methodBase, bool recursive, T ifCannotConvert)
|
||||||
@@ -70,7 +70,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
|
|
||||||
protected T Resolve<T>(string propertyTypeAlias, bool recursive, T ifCannotConvert)
|
protected T Resolve<T>(string propertyTypeAlias, bool recursive, T ifCannotConvert)
|
||||||
{
|
{
|
||||||
return Content.GetPropertyValue<T>(propertyTypeAlias, recursive, ifCannotConvert);
|
return WrappedContentInternal.GetPropertyValue<T>(propertyTypeAlias, recursive, ifCannotConvert);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
if (constructorInfo == null)
|
if (constructorInfo == null)
|
||||||
throw new Exception("No valid constructor found");
|
throw new Exception("No valid constructor found");
|
||||||
|
|
||||||
return (T) constructorInfo.Invoke(new object[] {Content.Parent});
|
return (T) constructorInfo.Invoke(new object[] {WrappedContentInternal.Parent});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<T> Children<T>(MethodBase methodBase) where T : TypedModelBase
|
protected IEnumerable<T> Children<T>(MethodBase methodBase) where T : TypedModelBase
|
||||||
@@ -98,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
|
|
||||||
string singularizedDocTypeAlias = docTypeAlias.ToSingular();
|
string singularizedDocTypeAlias = docTypeAlias.ToSingular();
|
||||||
|
|
||||||
return Content.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias)
|
return WrappedContentInternal.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias)
|
||||||
.Select(x => (T)constructorInfo.Invoke(new object[] { x }));
|
.Select(x => (T)constructorInfo.Invoke(new object[] { x }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
|
|
||||||
string singularizedDocTypeAlias = docTypeAlias.ToSingular();
|
string singularizedDocTypeAlias = docTypeAlias.ToSingular();
|
||||||
|
|
||||||
return Content.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias)
|
return WrappedContentInternal.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias)
|
||||||
.Select(x => (T)constructorInfo.Invoke(new object[] { x }));
|
.Select(x => (T)constructorInfo.Invoke(new object[] { x }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
|||||||
|
|
||||||
string singularizedDocTypeAlias = docTypeAlias.ToSingular();
|
string singularizedDocTypeAlias = docTypeAlias.ToSingular();
|
||||||
|
|
||||||
return Content.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias)
|
return WrappedContentInternal.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias)
|
||||||
.Select(x => (T)constructorInfo.Invoke(new object[] { x }));
|
.Select(x => (T)constructorInfo.Invoke(new object[] { x }));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
angular.module("umbraco.directives").directive('nestedContentEditor', [
|
||||||
|
|
||||||
|
function () {
|
||||||
|
|
||||||
|
var link = function ($scope) {
|
||||||
|
|
||||||
|
// Clone the model because some property editors
|
||||||
|
// do weird things like updating and config values
|
||||||
|
// so we want to ensure we start from a fresh every
|
||||||
|
// time, we'll just sync the value back when we need to
|
||||||
|
$scope.model = angular.copy($scope.ngModel);
|
||||||
|
$scope.nodeContext = $scope.model;
|
||||||
|
|
||||||
|
// Find the selected tab
|
||||||
|
var selectedTab = $scope.model.tabs[0];
|
||||||
|
|
||||||
|
if ($scope.tabAlias) {
|
||||||
|
angular.forEach($scope.model.tabs, function (tab) {
|
||||||
|
if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) {
|
||||||
|
selectedTab = tab;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.tab = selectedTab;
|
||||||
|
|
||||||
|
// Listen for sync request
|
||||||
|
var unsubscribe = $scope.$on("ncSyncVal", function (ev, args) {
|
||||||
|
if (args.key === $scope.model.key) {
|
||||||
|
|
||||||
|
// Tell inner controls we are submitting
|
||||||
|
$scope.$broadcast("formSubmitting", { scope: $scope });
|
||||||
|
|
||||||
|
// Sync the values back
|
||||||
|
angular.forEach($scope.ngModel.tabs, function (tab) {
|
||||||
|
if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) {
|
||||||
|
|
||||||
|
var localPropsMap = selectedTab.properties.reduce(function (map, obj) {
|
||||||
|
map[obj.alias] = obj;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
angular.forEach(tab.properties, function (prop) {
|
||||||
|
if (localPropsMap.hasOwnProperty(prop.alias)) {
|
||||||
|
prop.value = localPropsMap[prop.alias].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$on('$destroy', function () {
|
||||||
|
unsubscribe();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: "E",
|
||||||
|
replace: true,
|
||||||
|
templateUrl: Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/views/propertyeditors/nestedcontent/nestedcontent.editor.html",
|
||||||
|
scope: {
|
||||||
|
ngModel: '=',
|
||||||
|
tabAlias: '='
|
||||||
|
},
|
||||||
|
link: link
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
//angular.module("umbraco.directives").directive('nestedContentSubmitWatcher', function () {
|
||||||
|
// var link = function (scope) {
|
||||||
|
// // call the load callback on scope to obtain the ID of this submit watcher
|
||||||
|
// var id = scope.loadCallback();
|
||||||
|
// scope.$on("formSubmitting", function (ev, args) {
|
||||||
|
// // on the "formSubmitting" event, call the submit callback on scope to notify the nestedContent controller to do it's magic
|
||||||
|
// if (id === scope.activeSubmitWatcher) {
|
||||||
|
// scope.submitCallback();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// restrict: "E",
|
||||||
|
// replace: true,
|
||||||
|
// template: "",
|
||||||
|
// scope: {
|
||||||
|
// loadCallback: '=',
|
||||||
|
// submitCallback: '=',
|
||||||
|
// activeSubmitWatcher: '='
|
||||||
|
// },
|
||||||
|
// link: link
|
||||||
|
// }
|
||||||
|
//});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Filter to take a node id and grab it's name instead
|
||||||
|
// Usage: {{ pickerAlias | ncNodeName }}
|
||||||
|
|
||||||
|
// Cache for node names so we don't make a ton of requests
|
||||||
|
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") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentNode = editorState.getCurrent();
|
||||||
|
|
||||||
|
// Ensure a unique cache per editor instance
|
||||||
|
var key = "ncNodeName_" + currentNode.key;
|
||||||
|
if (ncNodeNameCache.id !== key) {
|
||||||
|
ncNodeNameCache.id = key;
|
||||||
|
ncNodeNameCache.keys = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if there is a value in the cache and use that
|
||||||
|
if (ncNodeNameCache.keys[input]) {
|
||||||
|
return ncNodeNameCache.keys[input];
|
||||||
|
}
|
||||||
|
|
||||||
|
// No value, so go fetch one
|
||||||
|
// We'll put a temp value in the cache though so we don't
|
||||||
|
// make a load of requests while we wait for a response
|
||||||
|
ncNodeNameCache.keys[input] = "Loading...";
|
||||||
|
|
||||||
|
entityResource.getById(input, "Document")
|
||||||
|
.then(function (ent) {
|
||||||
|
ncNodeNameCache.keys[input] = ent.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the current value for now
|
||||||
|
return ncNodeNameCache.keys[input];
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
angular.module('umbraco.resources').factory('Umbraco.PropertyEditors.NestedContent.Resources',
|
||||||
|
function ($q, $http, umbRequestHelper) {
|
||||||
|
return {
|
||||||
|
getContentTypes: function () {
|
||||||
|
var url = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + "/backoffice/UmbracoApi/NestedContent/GetContentTypes";
|
||||||
|
return umbRequestHelper.resourcePromise(
|
||||||
|
$http.get(url),
|
||||||
|
'Failed to retrieve content types'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -120,6 +120,7 @@
|
|||||||
@import "components/umb-querybuilder.less";
|
@import "components/umb-querybuilder.less";
|
||||||
@import "components/umb-pagination.less";
|
@import "components/umb-pagination.less";
|
||||||
@import "components/umb-mini-list-view.less";
|
@import "components/umb-mini-list-view.less";
|
||||||
|
@import "components/umb-nested-content.less";
|
||||||
|
|
||||||
@import "components/buttons/umb-button.less";
|
@import "components/buttons/umb-button.less";
|
||||||
@import "components/buttons/umb-button-group.less";
|
@import "components/buttons/umb-button-group.less";
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
.nested-content
|
||||||
|
{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__item
|
||||||
|
{
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
border-top: solid 1px transparent;
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__item--active:not(.nested-content__item--single)
|
||||||
|
{
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__item.ui-sortable-placeholder
|
||||||
|
{
|
||||||
|
background: #f8f8f8;
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
visibility: visible !important;
|
||||||
|
height: 55px;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__item--single > .nested-content__content
|
||||||
|
{
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__item--single > .nested-content__content > .umb-pane
|
||||||
|
{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__header-bar
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__heading
|
||||||
|
{
|
||||||
|
float: left;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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
|
||||||
|
{
|
||||||
|
margin: -6px 0;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transition: opacity .15s ease-in-out;
|
||||||
|
-moz-transition: opacity .15s ease-in-out;
|
||||||
|
-webkit-transition: opacity .15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__header-bar:hover .nested-content__icons,
|
||||||
|
.nested-content__item--active > .nested-content__header-bar .nested-content__icons
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__icon,
|
||||||
|
.nested-content__icon.nested-content__icon--disabled:hover
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #b6b6b6;
|
||||||
|
border-radius: 200px;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__icon:hover,
|
||||||
|
.nested-content__icon--active
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
background: #2e8aea;
|
||||||
|
border-color: #2e8aea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__icon .icon,
|
||||||
|
.nested-content__icon.nested-content__icon--disabled:hover .icon
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
font-size: 16px !important;
|
||||||
|
color: #5f5f5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__icon:hover .icon,
|
||||||
|
.nested-content__icon--active .icon
|
||||||
|
{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__icon--disabled
|
||||||
|
{
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.nested-content__footer-bar
|
||||||
|
{
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__content
|
||||||
|
{
|
||||||
|
border-bottom: 1px dashed #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__content .umb-control-group {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__item.ui-sortable-helper .nested-content__content
|
||||||
|
{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__help-text
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px 10px 20px;
|
||||||
|
clear: both;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__doctypepicker table input, .nested-content__doctypepicker table select {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__doctypepicker table td.icon-navigation, .nested-content__doctypepicker i.nested-content__help-icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #CCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__doctypepicker table td.icon-navigation:hover, .nested-content__doctypepicker i.nested-content__help-icon:hover {
|
||||||
|
color: #343434;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nested-content__doctypepicker i.nested-content__help-icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-horizontal .nested-content--narrow .controls-row
|
||||||
|
{
|
||||||
|
margin-left: 40% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-horizontal .nested-content--narrow .controls-row .umb-textstring,
|
||||||
|
.form-horizontal .nested-content--narrow .controls-row .umb-textarea
|
||||||
|
{
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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%);
|
||||||
|
}
|
||||||
@@ -0,0 +1,417 @@
|
|||||||
|
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [
|
||||||
|
|
||||||
|
"$scope",
|
||||||
|
"Umbraco.PropertyEditors.NestedContent.Resources",
|
||||||
|
|
||||||
|
function ($scope, ncResources) {
|
||||||
|
|
||||||
|
$scope.add = function () {
|
||||||
|
$scope.model.value.push({
|
||||||
|
// As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
|
||||||
|
// For good measure we'll also prefix the tab alias "nc"
|
||||||
|
ncAlias: "",
|
||||||
|
ncTabAlias: "",
|
||||||
|
nameTemplate: ""
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.remove = function (index) {
|
||||||
|
$scope.model.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.sortableOptions = {
|
||||||
|
axis: 'y',
|
||||||
|
cursor: "move",
|
||||||
|
handle: ".icon-navigation"
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.selectedDocTypeTabs = {};
|
||||||
|
|
||||||
|
ncResources.getContentTypes().then(function (docTypes) {
|
||||||
|
$scope.model.docTypes = docTypes;
|
||||||
|
|
||||||
|
// Populate document type tab dictionary
|
||||||
|
docTypes.forEach(function (value) {
|
||||||
|
$scope.selectedDocTypeTabs[value.alias] = value.tabs;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!$scope.model.value) {
|
||||||
|
$scope.model.value = [];
|
||||||
|
$scope.add();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [
|
||||||
|
|
||||||
|
"$scope",
|
||||||
|
"$interpolate",
|
||||||
|
"$filter",
|
||||||
|
"$timeout",
|
||||||
|
"contentResource",
|
||||||
|
"localizationService",
|
||||||
|
"iconHelper",
|
||||||
|
"Umbraco.PropertyEditors.NestedContent.Resources",
|
||||||
|
|
||||||
|
function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, ncResources) {
|
||||||
|
|
||||||
|
//$scope.model.config.contentTypes;
|
||||||
|
//$scope.model.config.minItems;
|
||||||
|
//$scope.model.config.maxItems;
|
||||||
|
//console.log($scope);
|
||||||
|
|
||||||
|
var inited = false;
|
||||||
|
|
||||||
|
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||||
|
contentType.nameExp = !!contentType.nameTemplate
|
||||||
|
? $interpolate(contentType.nameTemplate)
|
||||||
|
: undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.editIconTitle = '';
|
||||||
|
$scope.moveIconTitle = '';
|
||||||
|
$scope.deleteIconTitle = '';
|
||||||
|
|
||||||
|
// localize the edit icon title
|
||||||
|
localizationService.localize('general_edit').then(function (value) {
|
||||||
|
$scope.editIconTitle = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// localize the delete icon title
|
||||||
|
localizationService.localize('general_delete').then(function (value) {
|
||||||
|
$scope.deleteIconTitle = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// localize the move icon title
|
||||||
|
localizationService.localize('actions_move').then(function (value) {
|
||||||
|
$scope.moveIconTitle = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.nodes = [];
|
||||||
|
$scope.currentNode = undefined;
|
||||||
|
$scope.realCurrentNode = undefined;
|
||||||
|
$scope.scaffolds = undefined;
|
||||||
|
$scope.sorting = false;
|
||||||
|
|
||||||
|
$scope.minItems = $scope.model.config.minItems || 0;
|
||||||
|
$scope.maxItems = $scope.model.config.maxItems || 0;
|
||||||
|
|
||||||
|
if ($scope.maxItems == 0)
|
||||||
|
$scope.maxItems = 1000;
|
||||||
|
|
||||||
|
$scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
|
||||||
|
$scope.showIcons = $scope.model.config.showIcons || true;
|
||||||
|
$scope.wideMode = $scope.model.config.hideLabel == "1";
|
||||||
|
|
||||||
|
$scope.overlayMenu = {
|
||||||
|
show: false,
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.openNodeTypePicker = function (event) {
|
||||||
|
if ($scope.nodes.length >= $scope.maxItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this could be used for future limiting on node types
|
||||||
|
$scope.overlayMenu.scaffolds = [];
|
||||||
|
_.each($scope.scaffolds, function (scaffold) {
|
||||||
|
$scope.overlayMenu.scaffolds.push({
|
||||||
|
alias: scaffold.contentTypeAlias,
|
||||||
|
name: scaffold.contentTypeName,
|
||||||
|
icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($scope.overlayMenu.scaffolds.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scope.overlayMenu.scaffolds.length == 1) {
|
||||||
|
// only one scaffold type - no need to display the picker
|
||||||
|
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.overlayMenu.show = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.closeNodeTypePicker = function () {
|
||||||
|
$scope.overlayMenu.show = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.editNode = function (idx) {
|
||||||
|
if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
|
||||||
|
$scope.currentNode = undefined;
|
||||||
|
} else {
|
||||||
|
$scope.currentNode = $scope.nodes[idx];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteNode = function (idx) {
|
||||||
|
if ($scope.nodes.length > $scope.model.config.minItems) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getName = function (idx) {
|
||||||
|
|
||||||
|
var name = "Item " + (idx + 1);
|
||||||
|
|
||||||
|
if ($scope.model.value[idx]) {
|
||||||
|
|
||||||
|
var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);
|
||||||
|
|
||||||
|
if (contentType != null && contentType.nameExp) {
|
||||||
|
// Run the expression against the stored dictionary value, NOT the node object
|
||||||
|
var item = $scope.model.value[idx];
|
||||||
|
|
||||||
|
// Add a temporary index property
|
||||||
|
item['$index'] = (idx + 1);
|
||||||
|
|
||||||
|
var newName = contentType.nameExp(item);
|
||||||
|
if (newName && (newName = $.trim(newName))) {
|
||||||
|
name = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the index property as we don't want to persist it
|
||||||
|
delete item['$index'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the nodes actual name value
|
||||||
|
if ($scope.nodes[idx].name !== name) {
|
||||||
|
$scope.nodes[idx].name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getIcon = function (idx) {
|
||||||
|
var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
|
||||||
|
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.sortableOptions = {
|
||||||
|
axis: 'y',
|
||||||
|
cursor: "move",
|
||||||
|
handle: ".nested-content__icon--move",
|
||||||
|
start: function (ev, ui) {
|
||||||
|
// Yea, yea, we shouldn't modify the dom, sue me
|
||||||
|
$("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
|
||||||
|
tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
|
||||||
|
$(this).css("visibility", "hidden");
|
||||||
|
});
|
||||||
|
$scope.$apply(function () {
|
||||||
|
$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'));
|
||||||
|
$(this).css("visibility", "visible");
|
||||||
|
});
|
||||||
|
$scope.$apply(function () {
|
||||||
|
$scope.sorting = false;
|
||||||
|
updateModel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.getScaffold = function (alias) {
|
||||||
|
return _.find($scope.scaffolds, function (scaffold) {
|
||||||
|
return scaffold.contentTypeAlias == alias;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.getContentTypeConfig = function (alias) {
|
||||||
|
return _.find($scope.model.config.contentTypes, function (contentType) {
|
||||||
|
return contentType.ncAlias == alias;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
var scaffoldsLoaded = 0;
|
||||||
|
$scope.scaffolds = [];
|
||||||
|
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||||
|
contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
|
||||||
|
// remove all tabs except the specified tab
|
||||||
|
var tab = _.find(scaffold.tabs, function (tab) {
|
||||||
|
return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == "");
|
||||||
|
});
|
||||||
|
scaffold.tabs = [];
|
||||||
|
if (tab) {
|
||||||
|
scaffold.tabs.push(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the scaffold object
|
||||||
|
$scope.scaffolds.push(scaffold);
|
||||||
|
|
||||||
|
scaffoldsLoaded++;
|
||||||
|
initIfAllScaffoldsHaveLoaded();
|
||||||
|
}, function (error) {
|
||||||
|
scaffoldsLoaded++;
|
||||||
|
initIfAllScaffoldsHaveLoaded();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var initIfAllScaffoldsHaveLoaded = function () {
|
||||||
|
// Initialize when all scaffolds have loaded
|
||||||
|
if ($scope.model.config.contentTypes.length == scaffoldsLoaded) {
|
||||||
|
// Because we're loading the scaffolds async one at a time, we need to
|
||||||
|
// sort them explicitly according to the sort order defined by the data type.
|
||||||
|
var contentTypeAliases = [];
|
||||||
|
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||||
|
contentTypeAliases.push(contentType.ncAlias);
|
||||||
|
});
|
||||||
|
$scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) {
|
||||||
|
return contentTypeAliases.indexOf(s.contentTypeAlias);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert stored nodes
|
||||||
|
if ($scope.model.value) {
|
||||||
|
for (var i = 0; i < $scope.model.value.length; i++) {
|
||||||
|
var item = $scope.model.value[i];
|
||||||
|
var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
|
||||||
|
if (scaffold == null) {
|
||||||
|
// No such scaffold - the content type might have been deleted. We need to skip it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
initNode(scaffold, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce min items
|
||||||
|
if ($scope.nodes.length < $scope.model.config.minItems) {
|
||||||
|
for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
|
||||||
|
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is only one item, set it as current node
|
||||||
|
if ($scope.singleMode || ($scope.nodes.length == 1 && $scope.maxItems == 1)) {
|
||||||
|
$scope.currentNode = $scope.nodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
inited = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var initNode = function (scaffold, item) {
|
||||||
|
var node = angular.copy(scaffold);
|
||||||
|
|
||||||
|
node.key = guid();
|
||||||
|
node.ncContentTypeAlias = scaffold.contentTypeAlias;
|
||||||
|
|
||||||
|
for (var t = 0; t < node.tabs.length; t++) {
|
||||||
|
var tab = node.tabs[t];
|
||||||
|
for (var p = 0; p < tab.properties.length; p++) {
|
||||||
|
var prop = tab.properties[p];
|
||||||
|
prop.propertyAlias = prop.alias;
|
||||||
|
prop.alias = $scope.model.alias + "___" + prop.alias;
|
||||||
|
// Force validation to occur server side as this is the
|
||||||
|
// only way we can have consistancy between mandatory and
|
||||||
|
// regex validation messages. Not ideal, but it works.
|
||||||
|
prop.validation = {
|
||||||
|
mandatory: false,
|
||||||
|
pattern: ""
|
||||||
|
};
|
||||||
|
if (item) {
|
||||||
|
if (item[prop.propertyAlias]) {
|
||||||
|
prop.value = item[prop.propertyAlias];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.nodes.push(node);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateModel = function () {
|
||||||
|
if ($scope.realCurrentNode) {
|
||||||
|
$scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
|
||||||
|
}
|
||||||
|
if (inited) {
|
||||||
|
var newValues = [];
|
||||||
|
for (var i = 0; i < $scope.nodes.length; i++) {
|
||||||
|
var node = $scope.nodes[i];
|
||||||
|
var newValue = {
|
||||||
|
key: node.key,
|
||||||
|
name: node.name,
|
||||||
|
ncContentTypeAlias: node.ncContentTypeAlias
|
||||||
|
};
|
||||||
|
for (var t = 0; t < node.tabs.length; t++) {
|
||||||
|
var tab = node.tabs[t];
|
||||||
|
for (var p = 0; p < tab.properties.length; p++) {
|
||||||
|
var prop = tab.properties[p];
|
||||||
|
if (typeof prop.value !== "function") {
|
||||||
|
newValue[prop.propertyAlias] = prop.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newValues.push(newValue);
|
||||||
|
}
|
||||||
|
$scope.model.value = newValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch("currentNode", function (newVal) {
|
||||||
|
updateModel();
|
||||||
|
$scope.realCurrentNode = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
|
||||||
|
updateModel();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$on('$destroy', function () {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return _p8() + _p8(true) + _p8(true) + _p8();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<div id="{{model.alias}}" class="nested-content__doctypepicker" ng-controller="Umbraco.PropertyEditors.NestedContent.DocTypePickerController">
|
||||||
|
<div>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th/>
|
||||||
|
<th>
|
||||||
|
Document Type
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Tab
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Name Template
|
||||||
|
</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody ui-sortable="sortableOptions" ng-model="model.value">
|
||||||
|
<tr ng-repeat="config in model.value">
|
||||||
|
<td class="icon icon-navigation">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select id="{{model.alias}}_doctype_select"
|
||||||
|
ng-options="dt.alias as dt.name for dt in model.docTypes | orderBy: 'name'"
|
||||||
|
ng-model="config.ncAlias" required></select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select id="{{model.alias}}_tab_select"
|
||||||
|
ng-options="t for t in selectedDocTypeTabs[config.ncAlias]"
|
||||||
|
ng-model="config.ncTabAlias" required></select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" ng-model="config.nameTemplate" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn-danger" ng-click="remove($index)" ng-show="model.value.length > 1">Remove</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<a class="btn" ng-click="add()">Add</a>
|
||||||
|
<i class="icon icon-help-alt medium nested-content__help-icon" ng-click="showHelpText = !showHelpText"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="nested-content__help-text" ng-show="showHelpText">
|
||||||
|
<p>
|
||||||
|
<b>Tab:</b><br/>
|
||||||
|
Select the tab who's properties should be displayed. If left blank, the first tab on the doc type will be used.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Name template:</b><br/>
|
||||||
|
Enter an angular expression to evaluate against each item for its name. Use <code ng-non-bindable>{{$index}}</code> to display the item index
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="umb-pane">
|
||||||
|
|
||||||
|
<umb-property
|
||||||
|
property="property"
|
||||||
|
ng-repeat="property in tab.properties">
|
||||||
|
<umb-editor model="property"></umb-editor>
|
||||||
|
</umb-property>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<div id="nested-content--{{model.id}}" class="nested-content"
|
||||||
|
ng-controller="Umbraco.PropertyEditors.NestedContent.PropertyEditorController"
|
||||||
|
ng-class="{'nested-content--narrow':!wideMode, 'nested-content--wide':wideMode}">
|
||||||
|
<ng-form>
|
||||||
|
|
||||||
|
<div class="nested-content__items" ng-hide="nodes.length == 0" ui-sortable="sortableOptions" ng-model="nodes">
|
||||||
|
|
||||||
|
<div class="nested-content__item" ng-repeat="node in nodes" ng-class="{ 'nested-content__item--active' : $parent.realCurrentNode.key == node.key, 'nested-content__item--single' : $parent.singleMode }">
|
||||||
|
|
||||||
|
<div class="nested-content__header-bar" ng-click="$parent.editNode($index)" ng-hide="$parent.singleMode">
|
||||||
|
|
||||||
|
<div class="nested-content__heading"><i ng-if="showIcons" class="icon" ng-class="$parent.getIcon($index)"></i><span ng-bind="$parent.getName($index)"></span></div>
|
||||||
|
|
||||||
|
<div class="nested-content__icons">
|
||||||
|
<a class="nested-content__icon nested-content__icon--edit" title="{{editIconTitle}}" ng-class="{ 'nested-content__icon--active' : $parent.realCurrentNode.id == node.id }" ng-click="$parent.editNode($index); $event.stopPropagation();" ng-show="$parent.maxItems > 1" prevent-default>
|
||||||
|
<i class="icon icon-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a class="nested-content__icon nested-content__icon--move" title="{{moveIconTitle}}" ng-click="$event.stopPropagation();" ng-show="$parent.nodes.length > 1" prevent-default>
|
||||||
|
<i class="icon icon-navigation"></i>
|
||||||
|
</a>
|
||||||
|
<a class="nested-content__icon nested-content__icon--delete" title="{{deleteIconTitle}}" ng-class="{ 'nested-content__icon--disabled': $parent.nodes.length <= $parent.minItems }" ng-click="$parent.deleteNode($index); $event.stopPropagation();" prevent-default>
|
||||||
|
<i class="icon icon-trash"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nested-content__content" ng-if="$parent.realCurrentNode.key == node.key && !$parent.sorting">
|
||||||
|
<nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nested-content__help-text" ng-show="nodes.length == 0">
|
||||||
|
<localize key="grid_addElement"></localize>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nested-content__footer-bar" ng-hide="nodes.length >= maxItems">
|
||||||
|
<a class="nested-content__icon" ng-click="openNodeTypePicker($event)" prevent-default>
|
||||||
|
<i class="icon icon-add"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usky-grid nested-content__node-type-picker" ng-if="overlayMenu.show">
|
||||||
|
<div class="cell-tools-menu" ng-style="overlayMenu.style" style="margin: 0;" delayed-mouseleave="closeNodeTypePicker()" on-delayed-mouseleave="closeNodeTypePicker()">
|
||||||
|
<h5>
|
||||||
|
<localize key="grid_insertControl" />
|
||||||
|
</h5>
|
||||||
|
<ul class="elements">
|
||||||
|
<li ng-repeat="scaffold in overlayMenu.scaffolds">
|
||||||
|
<a ng-click="addNode(scaffold.alias)" href>
|
||||||
|
<i class="icon {{scaffold.icon}}"></i>
|
||||||
|
{{scaffold.name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-form>
|
||||||
|
</div>
|
||||||
@@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using Umbraco.Core.Models;
|
using Umbraco.Core.Models;
|
||||||
using Umbraco.Core.Models.PublishedContent;
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||||
|
using Umbraco.Web.PropertyEditors;
|
||||||
using Umbraco.Web.PropertyEditors.ValueConverters;
|
using Umbraco.Web.PropertyEditors.ValueConverters;
|
||||||
|
|
||||||
|
|
||||||
@@ -13,12 +14,12 @@ namespace Umbraco.Web.Cache
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A cache refresher to ensure member cache is updated when members change
|
/// A cache refresher to ensure member cache is updated when members change
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DataTypeCacheRefresher : JsonCacheRefresherBase<DataTypeCacheRefresher>
|
public sealed class DataTypeCacheRefresher : JsonCacheRefresherBase<DataTypeCacheRefresher>
|
||||||
{
|
{
|
||||||
|
|
||||||
#region Static helpers
|
#region Static helpers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the json to a JsonPayload object
|
/// Converts the json to a JsonPayload object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -29,7 +30,7 @@ namespace Umbraco.Web.Cache
|
|||||||
var serializer = new JavaScriptSerializer();
|
var serializer = new JavaScriptSerializer();
|
||||||
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the custom Json payload used to refresh cache amongst the servers
|
/// Creates the custom Json payload used to refresh cache amongst the servers
|
||||||
@@ -43,7 +44,7 @@ namespace Umbraco.Web.Cache
|
|||||||
var json = serializer.Serialize(items);
|
var json = serializer.Serialize(items);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a macro to a jsonPayload object
|
/// Converts a macro to a jsonPayload object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -58,7 +59,7 @@ namespace Umbraco.Web.Cache
|
|||||||
};
|
};
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Sub classes
|
#region Sub classes
|
||||||
@@ -93,7 +94,7 @@ namespace Umbraco.Web.Cache
|
|||||||
//we need to clear the ContentType runtime cache since that is what caches the
|
//we need to clear the ContentType runtime cache since that is what caches the
|
||||||
// db data type to store the value against and anytime a datatype changes, this also might change
|
// db data type to store the value against and anytime a datatype changes, this also might change
|
||||||
// we basically need to clear all sorts of runtime caches here because so many things depend upon a data type
|
// we basically need to clear all sorts of runtime caches here because so many things depend upon a data type
|
||||||
|
|
||||||
ClearAllIsolatedCacheByEntityType<IContent>();
|
ClearAllIsolatedCacheByEntityType<IContent>();
|
||||||
ClearAllIsolatedCacheByEntityType<IContentType>();
|
ClearAllIsolatedCacheByEntityType<IContentType>();
|
||||||
ClearAllIsolatedCacheByEntityType<IMedia>();
|
ClearAllIsolatedCacheByEntityType<IMedia>();
|
||||||
@@ -104,14 +105,15 @@ namespace Umbraco.Web.Cache
|
|||||||
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey);
|
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey);
|
||||||
|
|
||||||
var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache<IDataTypeDefinition>();
|
var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache<IDataTypeDefinition>();
|
||||||
payloads.ForEach(payload =>
|
foreach (var payload in payloads)
|
||||||
{
|
{
|
||||||
//clears the prevalue cache
|
//clears the prevalue cache
|
||||||
if (dataTypeCache)
|
if (dataTypeCache)
|
||||||
dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id));
|
dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id));
|
||||||
|
|
||||||
PublishedContentType.ClearDataType(payload.Id);
|
PublishedContentType.ClearDataType(payload.Id);
|
||||||
});
|
NestedContentHelper.ClearCache(payload.Id);
|
||||||
|
}
|
||||||
|
|
||||||
TagsValueConverter.ClearCaches();
|
TagsValueConverter.ClearCaches();
|
||||||
MultipleMediaPickerPropertyConverter.ClearCaches();
|
MultipleMediaPickerPropertyConverter.ClearCaches();
|
||||||
|
|||||||
166
src/Umbraco.Web/Models/DetachedPublishedContent.cs
Normal file
166
src/Umbraco.Web/Models/DetachedPublishedContent.cs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Umbraco.Core;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.Models
|
||||||
|
{
|
||||||
|
public class DetachedPublishedContent : PublishedContentWithKeyBase
|
||||||
|
{
|
||||||
|
private readonly Guid _key;
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly PublishedContentType _contentType;
|
||||||
|
private readonly IEnumerable<IPublishedProperty> _properties;
|
||||||
|
private readonly int _sortOrder;
|
||||||
|
private readonly bool _isPreviewing;
|
||||||
|
private readonly IPublishedContent _containerNode;
|
||||||
|
|
||||||
|
public DetachedPublishedContent(
|
||||||
|
Guid key,
|
||||||
|
string name,
|
||||||
|
PublishedContentType contentType,
|
||||||
|
IEnumerable<IPublishedProperty> properties,
|
||||||
|
IPublishedContent containerNode = null,
|
||||||
|
int sortOrder = 0,
|
||||||
|
bool isPreviewing = false)
|
||||||
|
{
|
||||||
|
_key = key;
|
||||||
|
_name = name;
|
||||||
|
_contentType = contentType;
|
||||||
|
_properties = properties;
|
||||||
|
_sortOrder = sortOrder;
|
||||||
|
_isPreviewing = isPreviewing;
|
||||||
|
_containerNode = containerNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Guid Key
|
||||||
|
{
|
||||||
|
get { return _key; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Id
|
||||||
|
{
|
||||||
|
get { return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name
|
||||||
|
{
|
||||||
|
get { return _name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsDraft
|
||||||
|
{
|
||||||
|
get { return _isPreviewing; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PublishedItemType ItemType
|
||||||
|
{
|
||||||
|
get { return PublishedItemType.Content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override PublishedContentType ContentType
|
||||||
|
{
|
||||||
|
get { return _contentType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string DocumentTypeAlias
|
||||||
|
{
|
||||||
|
get { return _contentType.Alias; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int DocumentTypeId
|
||||||
|
{
|
||||||
|
get { return _contentType.Id; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ICollection<IPublishedProperty> Properties
|
||||||
|
{
|
||||||
|
get { return _properties.ToArray(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IPublishedProperty GetProperty(string alias)
|
||||||
|
{
|
||||||
|
return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IPublishedProperty GetProperty(string alias, bool recurse)
|
||||||
|
{
|
||||||
|
if (recurse)
|
||||||
|
throw new NotSupportedException();
|
||||||
|
|
||||||
|
return GetProperty(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IPublishedContent Parent
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<IPublishedContent> Children
|
||||||
|
{
|
||||||
|
get { return Enumerable.Empty<IPublishedContent>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int TemplateId
|
||||||
|
{
|
||||||
|
get { return 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int SortOrder
|
||||||
|
{
|
||||||
|
get { return _sortOrder; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string UrlName
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string WriterName
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.WriterName : null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string CreatorName
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.CreatorName : null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int WriterId
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.WriterId : 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int CreatorId
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.CreatorId : 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Path
|
||||||
|
{
|
||||||
|
get { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DateTime CreateDate
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.CreateDate : DateTime.MinValue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override DateTime UpdateDate
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.UpdateDate : DateTime.MinValue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Guid Version
|
||||||
|
{
|
||||||
|
get { return _containerNode != null ? _containerNode.Version : Guid.Empty; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Level
|
||||||
|
{
|
||||||
|
get { return 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/Umbraco.Web/Models/DetachedPublishedProperty.cs
Normal file
52
src/Umbraco.Web/Models/DetachedPublishedProperty.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.Models
|
||||||
|
{
|
||||||
|
internal class DetachedPublishedProperty : IPublishedProperty
|
||||||
|
{
|
||||||
|
private readonly PublishedPropertyType _propertyType;
|
||||||
|
private readonly object _rawValue;
|
||||||
|
private readonly Lazy<object> _sourceValue;
|
||||||
|
private readonly Lazy<object> _objectValue;
|
||||||
|
private readonly Lazy<object> _xpathValue;
|
||||||
|
private readonly bool _isPreview;
|
||||||
|
|
||||||
|
public DetachedPublishedProperty(PublishedPropertyType propertyType, object value)
|
||||||
|
: this(propertyType, value, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DetachedPublishedProperty(PublishedPropertyType propertyType, object value, bool isPreview)
|
||||||
|
{
|
||||||
|
_propertyType = propertyType;
|
||||||
|
_isPreview = isPreview;
|
||||||
|
|
||||||
|
_rawValue = value;
|
||||||
|
|
||||||
|
_sourceValue = new Lazy<object>(() => _propertyType.ConvertDataToSource(_rawValue, _isPreview));
|
||||||
|
_objectValue = new Lazy<object>(() => _propertyType.ConvertSourceToObject(_sourceValue.Value, _isPreview));
|
||||||
|
_xpathValue = new Lazy<object>(() => _propertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreview));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PropertyTypeAlias
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _propertyType.PropertyTypeAlias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasValue
|
||||||
|
{
|
||||||
|
get { return DataValue != null && DataValue.ToString().Trim().Length > 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public object DataValue { get { return _rawValue; } }
|
||||||
|
|
||||||
|
public object Value { get { return _objectValue.Value; } }
|
||||||
|
|
||||||
|
public object XPathValue { get { return _xpathValue.Value; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Umbraco.Web/PropertyEditors/NestedContentController.cs
Normal file
27
src/Umbraco.Web/PropertyEditors/NestedContentController.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Umbraco.Web.Editors;
|
||||||
|
using Umbraco.Web.Mvc;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.PropertyEditors
|
||||||
|
{
|
||||||
|
[PluginController("UmbracoApi")]
|
||||||
|
public class NestedContentController : UmbracoAuthorizedJsonController
|
||||||
|
{
|
||||||
|
[System.Web.Http.HttpGet]
|
||||||
|
public IEnumerable<object> GetContentTypes()
|
||||||
|
{
|
||||||
|
return Services.ContentTypeService.GetAllContentTypes()
|
||||||
|
.OrderBy(x => x.SortOrder)
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
id = x.Id,
|
||||||
|
guid = x.Key,
|
||||||
|
name = x.Name,
|
||||||
|
alias = x.Alias,
|
||||||
|
icon = x.Icon,
|
||||||
|
tabs = x.CompositionPropertyGroups.Select(y => y.Name).Distinct()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
131
src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs
Normal file
131
src/Umbraco.Web/PropertyEditors/NestedContentHelper.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Umbraco.Core;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.PropertyEditors
|
||||||
|
{
|
||||||
|
internal static class NestedContentHelper
|
||||||
|
{
|
||||||
|
private const string CacheKeyPrefix = "Umbraco.Web.PropertyEditors.NestedContent.GetPreValuesCollectionByDataTypeId_";
|
||||||
|
|
||||||
|
public static PreValueCollection GetPreValuesCollectionByDataTypeId(int dtdId)
|
||||||
|
{
|
||||||
|
var preValueCollection = (PreValueCollection)ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(
|
||||||
|
string.Concat(CacheKeyPrefix, dtdId),
|
||||||
|
() => ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtdId));
|
||||||
|
|
||||||
|
return preValueCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearCache(int id)
|
||||||
|
{
|
||||||
|
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(
|
||||||
|
string.Concat(CacheKeyPrefix, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetContentTypeAliasFromItem(JObject item)
|
||||||
|
{
|
||||||
|
var contentTypeAliasProperty = item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey];
|
||||||
|
if (contentTypeAliasProperty == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentTypeAliasProperty.ToObject<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IContentType GetContentTypeFromItem(JObject item)
|
||||||
|
{
|
||||||
|
var contentTypeAlias = GetContentTypeAliasFromItem(item);
|
||||||
|
if (string.IsNullOrEmpty(contentTypeAlias))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicationContext.Current.Services.ContentTypeService.GetContentType(contentTypeAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Conversion from v0.1.1 data formats
|
||||||
|
|
||||||
|
public static void ConvertItemValueFromV011(JObject item, int dtdId, ref PreValueCollection preValues)
|
||||||
|
{
|
||||||
|
var contentTypeAlias = GetContentTypeAliasFromItem(item);
|
||||||
|
if (contentTypeAlias != null)
|
||||||
|
{
|
||||||
|
// the item is already in >v0.1.1 format
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// old style (v0.1.1) data, let's attempt a conversion
|
||||||
|
// - get the prevalues (if they're not loaded already)
|
||||||
|
preValues = preValues ?? GetPreValuesCollectionByDataTypeId(dtdId);
|
||||||
|
|
||||||
|
// - convert the prevalues (if necessary)
|
||||||
|
ConvertPreValueCollectionFromV011(preValues);
|
||||||
|
|
||||||
|
// - get the content types prevalue as JArray
|
||||||
|
var preValuesAsDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value);
|
||||||
|
if (!preValuesAsDictionary.ContainsKey(ContentTypesPreValueKey) || string.IsNullOrEmpty(preValuesAsDictionary[ContentTypesPreValueKey]) != false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var preValueContentTypes = JArray.Parse(preValuesAsDictionary[ContentTypesPreValueKey]);
|
||||||
|
if (preValueContentTypes.Any())
|
||||||
|
{
|
||||||
|
// the only thing we can really do is assume that the item is the first available content type
|
||||||
|
item[NestedContentPropertyEditor.ContentTypeAliasPropertyKey] = preValueContentTypes.First().Value<string>("ncAlias");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ConvertPreValueCollectionFromV011(PreValueCollection preValueCollection)
|
||||||
|
{
|
||||||
|
if (preValueCollection == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
// the prevalues are already in >v0.1.1 format
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to parse the doc type guid
|
||||||
|
Guid guid;
|
||||||
|
if (Guid.TryParse(persistedPreValuesAsDictionary["docTypeGuid"], out guid) == false)
|
||||||
|
{
|
||||||
|
// this shouldn't happen... but just in case.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the content type
|
||||||
|
var contentType = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes().FirstOrDefault(c => c.Key == guid);
|
||||||
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a prevalue in the format expected by the new (>0.1.1) content type picker/configurator
|
||||||
|
preValueCollection.PreValuesAsDictionary[ContentTypesPreValueKey] = new PreValue(
|
||||||
|
string.Format(@"[{{""ncAlias"": ""{0}"", ""ncTabAlias"": ""{1}"", ""nameTemplate"": ""{2}"", }}]",
|
||||||
|
contentType.Alias,
|
||||||
|
persistedPreValuesAsDictionary["tabAlias"],
|
||||||
|
persistedPreValuesAsDictionary["nameTemplate"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ContentTypesPreValueKey
|
||||||
|
{
|
||||||
|
get { return NestedContentPropertyEditor.NestedContentPreValueEditor.ContentTypesPreValueKey; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
413
src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
Normal file
413
src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Umbraco.Core;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
using Umbraco.Core.Models.Editors;
|
||||||
|
using Umbraco.Core.PropertyEditors;
|
||||||
|
using Umbraco.Core.Services;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.PropertyEditors
|
||||||
|
{
|
||||||
|
[PropertyEditor(Constants.PropertyEditors.NestedContentAlias, "Nested Content", "nestedcontent", ValueType = "JSON", Group = "lists", Icon = "icon-thumbnail-list")]
|
||||||
|
public class NestedContentPropertyEditor : PropertyEditor
|
||||||
|
{
|
||||||
|
internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
|
||||||
|
|
||||||
|
private IDictionary<string, object> _defaultPreValues;
|
||||||
|
public override IDictionary<string, object> DefaultPreValues
|
||||||
|
{
|
||||||
|
get { return _defaultPreValues; }
|
||||||
|
set { _defaultPreValues = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public NestedContentPropertyEditor()
|
||||||
|
{
|
||||||
|
// Setup default values
|
||||||
|
_defaultPreValues = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{NestedContentPreValueEditor.ContentTypesPreValueKey, ""},
|
||||||
|
{"minItems", 0},
|
||||||
|
{"maxItems", 0},
|
||||||
|
{"confirmDeletes", "1"},
|
||||||
|
{"showIcons", "1"}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Pre Value Editor
|
||||||
|
|
||||||
|
protected override PreValueEditor CreatePreValueEditor()
|
||||||
|
{
|
||||||
|
return new NestedContentPreValueEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class NestedContentPreValueEditor : PreValueEditor
|
||||||
|
{
|
||||||
|
internal const string ContentTypesPreValueKey = "contentTypes";
|
||||||
|
|
||||||
|
[PreValueField(ContentTypesPreValueKey, "Doc Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the doc types to use as the data blueprint.")]
|
||||||
|
public string[] ContentTypes { get; set; }
|
||||||
|
|
||||||
|
[PreValueField("minItems", "Min Items", "number", Description = "Set the minimum number of items allowed.")]
|
||||||
|
public string MinItems { get; set; }
|
||||||
|
|
||||||
|
[PreValueField("maxItems", "Max Items", "number", Description = "Set the maximum number of items allowed.")]
|
||||||
|
public string MaxItems { get; set; }
|
||||||
|
|
||||||
|
[PreValueField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")]
|
||||||
|
public string ConfirmDeletes { get; set; }
|
||||||
|
|
||||||
|
[PreValueField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")]
|
||||||
|
public string ShowIcons { get; set; }
|
||||||
|
|
||||||
|
[PreValueField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")]
|
||||||
|
public string HideLabel { get; set; }
|
||||||
|
|
||||||
|
public override IDictionary<string, object> ConvertDbToEditor(IDictionary<string, object> defaultPreVals, PreValueCollection persistedPreVals)
|
||||||
|
{
|
||||||
|
// re-format old style (v0.1.1) pre values if necessary
|
||||||
|
NestedContentHelper.ConvertPreValueCollectionFromV011(persistedPreVals);
|
||||||
|
|
||||||
|
return base.ConvertDbToEditor(defaultPreVals, persistedPreVals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Value Editor
|
||||||
|
|
||||||
|
protected override PropertyValueEditor CreateValueEditor()
|
||||||
|
{
|
||||||
|
return new NestedContentPropertyValueEditor(base.CreateValueEditor());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class NestedContentPropertyValueEditor : PropertyValueEditorWrapper
|
||||||
|
{
|
||||||
|
public NestedContentPropertyValueEditor(PropertyValueEditor wrapped)
|
||||||
|
: base(wrapped)
|
||||||
|
{
|
||||||
|
Validators.Add(new NestedContentValidator());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ServiceContext Services
|
||||||
|
{
|
||||||
|
get { return ApplicationContext.Current.Services; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ConfigureForDisplay(PreValueCollection preValues)
|
||||||
|
{
|
||||||
|
base.ConfigureForDisplay(preValues);
|
||||||
|
|
||||||
|
var asDictionary = preValues.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value);
|
||||||
|
if (asDictionary.ContainsKey("hideLabel"))
|
||||||
|
{
|
||||||
|
var boolAttempt = asDictionary["hideLabel"].TryConvertTo<bool>();
|
||||||
|
if (boolAttempt.Success)
|
||||||
|
{
|
||||||
|
HideLabel = boolAttempt.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region DB to String
|
||||||
|
|
||||||
|
public override string ConvertDbToString(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
|
||||||
|
{
|
||||||
|
// Convert / validate value
|
||||||
|
if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString()))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var value = JsonConvert.DeserializeObject<List<object>>(property.Value.ToString());
|
||||||
|
if (value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Process value
|
||||||
|
PreValueCollection preValues = null;
|
||||||
|
for (var i = 0; i < value.Count; i++)
|
||||||
|
{
|
||||||
|
var o = value[i];
|
||||||
|
var propValues = ((JObject)o);
|
||||||
|
|
||||||
|
// convert from old style (v0.1.1) data format if necessary
|
||||||
|
NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues);
|
||||||
|
|
||||||
|
var contentType = NestedContentHelper.GetContentTypeFromItem(propValues);
|
||||||
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray();
|
||||||
|
|
||||||
|
foreach (var propKey in propValueKeys)
|
||||||
|
{
|
||||||
|
var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey);
|
||||||
|
if (propType == null)
|
||||||
|
{
|
||||||
|
if (IsSystemPropertyKey(propKey) == false)
|
||||||
|
{
|
||||||
|
// Property missing so just delete the value
|
||||||
|
propValues[propKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create a fake property using the property abd 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, and store it back
|
||||||
|
propValues[propKey] = propEditor.ValueEditor.ConvertDbToString(prop, propType, dataTypeService);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// https://github.com/umco/umbraco-nested-content/issues/111
|
||||||
|
// Catch any invalid cast operations as likely means courier failed due to missing
|
||||||
|
// or trashed item so couldn't convert a guid back to an int
|
||||||
|
|
||||||
|
propValues[propKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the value on the property
|
||||||
|
property.Value = JsonConvert.SerializeObject(value);
|
||||||
|
|
||||||
|
// Pass the call down
|
||||||
|
return base.ConvertDbToString(property, propertyType, dataTypeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region DB to Editor
|
||||||
|
|
||||||
|
public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
|
||||||
|
{
|
||||||
|
if (property.Value == null || string.IsNullOrWhiteSpace(property.Value.ToString()))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var value = JsonConvert.DeserializeObject<List<object>>(property.Value.ToString());
|
||||||
|
if (value == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Process value
|
||||||
|
PreValueCollection preValues = null;
|
||||||
|
for (var i = 0; i < value.Count; i++)
|
||||||
|
{
|
||||||
|
var o = value[i];
|
||||||
|
var propValues = ((JObject)o);
|
||||||
|
|
||||||
|
// convert from old style (v0.1.1) data format if necessary
|
||||||
|
NestedContentHelper.ConvertItemValueFromV011(propValues, propertyType.DataTypeDefinitionId, ref preValues);
|
||||||
|
|
||||||
|
var contentType = NestedContentHelper.GetContentTypeFromItem(propValues);
|
||||||
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray();
|
||||||
|
|
||||||
|
foreach (var propKey in propValueKeys)
|
||||||
|
{
|
||||||
|
var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey);
|
||||||
|
if (propType == null)
|
||||||
|
{
|
||||||
|
if (IsSystemPropertyKey(propKey) == false)
|
||||||
|
{
|
||||||
|
// Property missing so just delete the value
|
||||||
|
propValues[propKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 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, dataTypeService);
|
||||||
|
|
||||||
|
// Store the value back
|
||||||
|
propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// https://github.com/umco/umbraco-nested-content/issues/111
|
||||||
|
// Catch any invalid cast operations as likely means courier failed due to missing
|
||||||
|
// or trashed item so couldn't convert a guid back to an int
|
||||||
|
|
||||||
|
propValues[propKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the value on the property
|
||||||
|
property.Value = JsonConvert.SerializeObject(value);
|
||||||
|
|
||||||
|
// Pass the call down
|
||||||
|
return base.ConvertDbToEditor(property, propertyType, dataTypeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Editor to DB
|
||||||
|
|
||||||
|
public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
|
||||||
|
{
|
||||||
|
if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var value = JsonConvert.DeserializeObject<List<object>>(editorValue.Value.ToString());
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Issue #38 - Keep recursive property lookups working
|
||||||
|
if (!value.Any())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Process value
|
||||||
|
for (var i = 0; i < value.Count; i++)
|
||||||
|
{
|
||||||
|
var o = value[i];
|
||||||
|
var propValues = ((JObject)o);
|
||||||
|
|
||||||
|
var contentType = NestedContentHelper.GetContentTypeFromItem(propValues);
|
||||||
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray();
|
||||||
|
|
||||||
|
foreach (var propKey in propValueKeys)
|
||||||
|
{
|
||||||
|
var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey);
|
||||||
|
if (propType == null)
|
||||||
|
{
|
||||||
|
if (IsSystemPropertyKey(propKey) == false)
|
||||||
|
{
|
||||||
|
// Property missing so just delete the value
|
||||||
|
propValues[propKey] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fetch the property types prevalue
|
||||||
|
var propPreValues = Services.DataTypeService.GetPreValuesCollectionByDataTypeId(
|
||||||
|
propType.DataTypeDefinitionId);
|
||||||
|
|
||||||
|
// Lookup the property editor
|
||||||
|
var propEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias);
|
||||||
|
|
||||||
|
// Create a fake content property data object
|
||||||
|
var contentPropData = new ContentPropertyData(
|
||||||
|
propValues[propKey], propPreValues,
|
||||||
|
new Dictionary<string, object>());
|
||||||
|
|
||||||
|
// Get the property editor to do it's conversion
|
||||||
|
var newValue = propEditor.ValueEditor.ConvertEditorToDb(contentPropData, propValues[propKey]);
|
||||||
|
|
||||||
|
// Store the value back
|
||||||
|
propValues[propKey] = (newValue == null) ? null : JToken.FromObject(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class NestedContentValidator : IPropertyValidator
|
||||||
|
{
|
||||||
|
public IEnumerable<ValidationResult> Validate(object rawValue, PreValueCollection preValues, PropertyEditor editor)
|
||||||
|
{
|
||||||
|
var value = JsonConvert.DeserializeObject<List<object>>(rawValue.ToString());
|
||||||
|
if (value == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
IDataTypeService dataTypeService = ApplicationContext.Current.Services.DataTypeService;
|
||||||
|
for (var i = 0; i < value.Count; i++)
|
||||||
|
{
|
||||||
|
var o = value[i];
|
||||||
|
var propValues = ((JObject)o);
|
||||||
|
|
||||||
|
var contentType = NestedContentHelper.GetContentTypeFromItem(propValues);
|
||||||
|
if (contentType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propValueKeys = propValues.Properties().Select(x => x.Name).ToArray();
|
||||||
|
|
||||||
|
foreach (var propKey in propValueKeys)
|
||||||
|
{
|
||||||
|
var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == propKey);
|
||||||
|
if (propType != null)
|
||||||
|
{
|
||||||
|
PreValueCollection propPrevalues = dataTypeService.GetPreValuesCollectionByDataTypeId(propType.DataTypeDefinitionId);
|
||||||
|
PropertyEditor propertyEditor = PropertyEditorResolver.Current.GetByAlias(propType.PropertyEditorAlias);
|
||||||
|
|
||||||
|
foreach (IPropertyValidator validator in propertyEditor.ValueEditor.Validators)
|
||||||
|
{
|
||||||
|
foreach (ValidationResult result in validator.Validate(propValues[propKey], propPrevalues, propertyEditor))
|
||||||
|
{
|
||||||
|
result.ErrorMessage = "Item " + (i + 1) + " '" + propType.Name + "' " + result.ErrorMessage;
|
||||||
|
yield return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check mandatory
|
||||||
|
if (propType.Mandatory)
|
||||||
|
{
|
||||||
|
if (propValues[propKey] == null)
|
||||||
|
yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be null", new[] { propKey });
|
||||||
|
else if (propValues[propKey].ToString().IsNullOrWhiteSpace())
|
||||||
|
yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be empty", new[] { propKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check regex
|
||||||
|
if (!propType.ValidationRegExp.IsNullOrWhiteSpace()
|
||||||
|
&& propValues[propKey] != null && !propValues[propKey].ToString().IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var regex = new Regex(propType.ValidationRegExp);
|
||||||
|
if (!regex.IsMatch(propValues[propKey].ToString()))
|
||||||
|
{
|
||||||
|
yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' is invalid, it does not match the correct pattern", new[] { propKey });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static bool IsSystemPropertyKey(string propKey)
|
||||||
|
{
|
||||||
|
return propKey == "name" || propKey == "key" || propKey == ContentTypeAliasPropertyKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Umbraco.Core.Logging;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
using Umbraco.Core.PropertyEditors;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||||
|
{
|
||||||
|
public class NestedContentManyValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta
|
||||||
|
{
|
||||||
|
public override bool IsConverter(PublishedPropertyType propertyType)
|
||||||
|
{
|
||||||
|
return propertyType.IsNestedContentProperty() && !propertyType.IsSingleNestedContentProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return propertyType.ConvertPropertyToNestedContent(source, preview);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogHelper.Error<NestedContentManyValueConverter>("Error converting value", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Type GetPropertyValueType(PublishedPropertyType propertyType)
|
||||||
|
{
|
||||||
|
return typeof(IEnumerable<IPublishedContent>);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue)
|
||||||
|
{
|
||||||
|
return PropertyCacheLevel.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Umbraco.Core;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
using Umbraco.Web.Models;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||||
|
{
|
||||||
|
internal static class NestedContentPublishedPropertyTypeExtensions
|
||||||
|
{
|
||||||
|
public static bool IsNestedContentProperty(this PublishedPropertyType publishedProperty)
|
||||||
|
{
|
||||||
|
return publishedProperty.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.NestedContentAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSingleNestedContentProperty(this PublishedPropertyType publishedProperty)
|
||||||
|
{
|
||||||
|
if (!publishedProperty.IsNestedContentProperty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(publishedProperty.DataTypeId);
|
||||||
|
var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value);
|
||||||
|
|
||||||
|
int minItems, maxItems;
|
||||||
|
return preValueDictionary.ContainsKey("minItems") &&
|
||||||
|
int.TryParse(preValueDictionary["minItems"], out minItems) && minItems == 1
|
||||||
|
&& preValueDictionary.ContainsKey("maxItems") &&
|
||||||
|
int.TryParse(preValueDictionary["maxItems"], out maxItems) && maxItems == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object ConvertPropertyToNestedContent(this PublishedPropertyType propertyType, object source, bool preview)
|
||||||
|
{
|
||||||
|
using (DisposableTimer.DebugDuration<PublishedPropertyType>(string.Format("ConvertPropertyToNestedContent ({0})", propertyType.DataTypeId)))
|
||||||
|
{
|
||||||
|
if (source != null && !source.ToString().IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var rawValue = JsonConvert.DeserializeObject<List<object>>(source.ToString());
|
||||||
|
var processedValue = new List<IPublishedContent>();
|
||||||
|
|
||||||
|
var preValueCollection = NestedContentHelper.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId);
|
||||||
|
var preValueDictionary = preValueCollection.PreValuesAsDictionary.ToDictionary(x => x.Key, x => x.Value.Value);
|
||||||
|
|
||||||
|
for (var i = 0; i < rawValue.Count; i++)
|
||||||
|
{
|
||||||
|
var item = (JObject)rawValue[i];
|
||||||
|
|
||||||
|
// Convert from old style (v.0.1.1) data format if necessary
|
||||||
|
// - Please note: This call has virtually no impact on rendering performance for new style (>v0.1.1).
|
||||||
|
// Even so, this should be removed eventually, when it's safe to assume that there is
|
||||||
|
// no longer any need for conversion.
|
||||||
|
NestedContentHelper.ConvertItemValueFromV011(item, propertyType.DataTypeId, ref preValueCollection);
|
||||||
|
|
||||||
|
var contentTypeAlias = NestedContentHelper.GetContentTypeAliasFromItem(item);
|
||||||
|
if (string.IsNullOrEmpty(contentTypeAlias))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var publishedContentType = PublishedContentType.Get(PublishedItemType.Content, contentTypeAlias);
|
||||||
|
if (publishedContentType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var propValues = item.ToObject<Dictionary<string, object>>();
|
||||||
|
var properties = new List<IPublishedProperty>();
|
||||||
|
|
||||||
|
foreach (var jProp in propValues)
|
||||||
|
{
|
||||||
|
var propType = publishedContentType.GetPropertyType(jProp.Key);
|
||||||
|
if (propType != null)
|
||||||
|
{
|
||||||
|
properties.Add(new DetachedPublishedProperty(propType, jProp.Value, preview));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse out the name manually
|
||||||
|
object nameObj = null;
|
||||||
|
if (propValues.TryGetValue("name", out nameObj))
|
||||||
|
{
|
||||||
|
// Do nothing, we just want to parse out the name if we can
|
||||||
|
}
|
||||||
|
|
||||||
|
object keyObj;
|
||||||
|
var key = Guid.Empty;
|
||||||
|
if (propValues.TryGetValue("key", out keyObj))
|
||||||
|
{
|
||||||
|
key = Guid.Parse(keyObj.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current request node we are embedded in
|
||||||
|
var pcr = UmbracoContext.Current == null ? null : UmbracoContext.Current.PublishedContentRequest;
|
||||||
|
var containerNode = pcr != null && pcr.HasPublishedContent ? pcr.PublishedContent : null;
|
||||||
|
|
||||||
|
// Create the model based on our implementation of IPublishedContent
|
||||||
|
IPublishedContent content = new DetachedPublishedContent(
|
||||||
|
key,
|
||||||
|
nameObj == null ? null : nameObj.ToString(),
|
||||||
|
publishedContentType,
|
||||||
|
properties.ToArray(),
|
||||||
|
containerNode,
|
||||||
|
i,
|
||||||
|
preview);
|
||||||
|
|
||||||
|
if (PublishedContentModelFactoryResolver.HasCurrent)
|
||||||
|
{
|
||||||
|
// 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())
|
||||||
|
{
|
||||||
|
return processedValue.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using Umbraco.Core.Logging;
|
||||||
|
using Umbraco.Core.Models;
|
||||||
|
using Umbraco.Core.Models.PublishedContent;
|
||||||
|
using Umbraco.Core.PropertyEditors;
|
||||||
|
|
||||||
|
namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||||
|
{
|
||||||
|
public class NestedContentSingleValueConverter : PropertyValueConverterBase, IPropertyValueConverterMeta
|
||||||
|
{
|
||||||
|
public override bool IsConverter(PublishedPropertyType propertyType)
|
||||||
|
{
|
||||||
|
return propertyType.IsSingleNestedContentProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return propertyType.ConvertPropertyToNestedContent(source, preview);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogHelper.Error<NestedContentSingleValueConverter>("Error converting value", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Type GetPropertyValueType(PublishedPropertyType propertyType)
|
||||||
|
{
|
||||||
|
return typeof(IPublishedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue)
|
||||||
|
{
|
||||||
|
return PropertyCacheLevel.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,12 @@ namespace Umbraco.Web
|
|||||||
|
|
||||||
public static Guid GetKey(this IPublishedContent content)
|
public static Guid GetKey(this IPublishedContent content)
|
||||||
{
|
{
|
||||||
|
var wrapped = content as PublishedContentWrapped;
|
||||||
|
while (wrapped != null)
|
||||||
|
{
|
||||||
|
content = wrapped.Unwrap();
|
||||||
|
wrapped = content as PublishedContentWrapped;
|
||||||
|
}
|
||||||
var contentWithKey = content as IPublishedContentWithKey;
|
var contentWithKey = content as IPublishedContentWithKey;
|
||||||
return contentWithKey == null ? Guid.Empty : contentWithKey.Key;
|
return contentWithKey == null ? Guid.Empty : contentWithKey.Key;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,7 +304,30 @@
|
|||||||
<Compile Include="ApplicationContextExtensions.cs" />
|
<Compile Include="ApplicationContextExtensions.cs" />
|
||||||
<Compile Include="AreaRegistrationContextExtensions.cs" />
|
<Compile Include="AreaRegistrationContextExtensions.cs" />
|
||||||
<Compile Include="BatchedDatabaseServerMessengerStartup.cs" />
|
<Compile Include="BatchedDatabaseServerMessengerStartup.cs" />
|
||||||
|
<Compile Include="Cache\ApplicationCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\ApplicationTreeCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\CacheRefresherEventHandler.cs" />
|
||||||
|
<Compile Include="Cache\ContentTypeCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\DataTypeCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\DictionaryCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\DistributedCache.cs" />
|
||||||
|
<Compile Include="Cache\DistributedCacheExtensions.cs" />
|
||||||
|
<Compile Include="Cache\DomainCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\LanguageCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\MacroCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\MediaCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\MemberCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\MemberGroupCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\PageCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\PublicAccessCacheRefresher.cs" />
|
||||||
<Compile Include="Cache\RelationTypeCacheRefresher.cs" />
|
<Compile Include="Cache\RelationTypeCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\StylesheetCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\StylesheetPropertyCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\TemplateCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\UnpublishedPageCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\UserCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\UserPermissionsCacheRefresher.cs" />
|
||||||
|
<Compile Include="Cache\UserTypeCacheRefresher.cs" />
|
||||||
<Compile Include="Editors\BackOfficeNotificationsController.cs" />
|
<Compile Include="Editors\BackOfficeNotificationsController.cs" />
|
||||||
<Compile Include="Editors\EditorValidationResolver.cs" />
|
<Compile Include="Editors\EditorValidationResolver.cs" />
|
||||||
<Compile Include="Editors\EditorValidator.cs" />
|
<Compile Include="Editors\EditorValidator.cs" />
|
||||||
@@ -366,6 +389,8 @@
|
|||||||
<Compile Include="Models\ContentEditing\SimpleNotificationModel.cs" />
|
<Compile Include="Models\ContentEditing\SimpleNotificationModel.cs" />
|
||||||
<Compile Include="Models\ContentEditing\SnippetDisplay.cs" />
|
<Compile Include="Models\ContentEditing\SnippetDisplay.cs" />
|
||||||
<Compile Include="Models\ContentEditing\TemplateDisplay.cs" />
|
<Compile Include="Models\ContentEditing\TemplateDisplay.cs" />
|
||||||
|
<Compile Include="Models\DetachedPublishedContent.cs" />
|
||||||
|
<Compile Include="Models\DetachedPublishedProperty.cs" />
|
||||||
<Compile Include="Models\LocalPackageInstallModel.cs" />
|
<Compile Include="Models\LocalPackageInstallModel.cs" />
|
||||||
<Compile Include="Models\Mapping\CodeFileDisplayMapper.cs" />
|
<Compile Include="Models\Mapping\CodeFileDisplayMapper.cs" />
|
||||||
<Compile Include="Models\Mapping\ContentTypeModelMapperExtensions.cs" />
|
<Compile Include="Models\Mapping\ContentTypeModelMapperExtensions.cs" />
|
||||||
@@ -402,6 +427,9 @@
|
|||||||
<Compile Include="PropertyEditors\MediaPicker2PropertyEditor.cs" />
|
<Compile Include="PropertyEditors\MediaPicker2PropertyEditor.cs" />
|
||||||
<Compile Include="PropertyEditors\MemberPicker2PropertyEditor.cs" />
|
<Compile Include="PropertyEditors\MemberPicker2PropertyEditor.cs" />
|
||||||
<Compile Include="PropertyEditors\MultiNodeTreePicker2PropertyEditor.cs" />
|
<Compile Include="PropertyEditors\MultiNodeTreePicker2PropertyEditor.cs" />
|
||||||
|
<Compile Include="PropertyEditors\NestedContentController.cs" />
|
||||||
|
<Compile Include="PropertyEditors\NestedContentHelper.cs" />
|
||||||
|
<Compile Include="PropertyEditors\NestedContentPropertyEditor.cs" />
|
||||||
<Compile Include="PropertyEditors\RelatedLinks2PropertyEditor.cs" />
|
<Compile Include="PropertyEditors\RelatedLinks2PropertyEditor.cs" />
|
||||||
<Compile Include="PropertyEditors\ValueConverters\ContentPickerPropertyConverter.cs" />
|
<Compile Include="PropertyEditors\ValueConverters\ContentPickerPropertyConverter.cs" />
|
||||||
<Compile Include="PropertyEditors\ParameterEditors\MultipleContentPickerParameterEditor.cs" />
|
<Compile Include="PropertyEditors\ParameterEditors\MultipleContentPickerParameterEditor.cs" />
|
||||||
@@ -412,6 +440,9 @@
|
|||||||
<Compile Include="PropertyEditors\ValueConverters\MemberPickerPropertyConverter.cs" />
|
<Compile Include="PropertyEditors\ValueConverters\MemberPickerPropertyConverter.cs" />
|
||||||
<Compile Include="PropertyEditors\ValueConverters\MultiNodeTreePickerPropertyConverter.cs" />
|
<Compile Include="PropertyEditors\ValueConverters\MultiNodeTreePickerPropertyConverter.cs" />
|
||||||
<Compile Include="PropertyEditors\ValueConverters\MultipleMediaPickerPropertyConverter.cs" />
|
<Compile Include="PropertyEditors\ValueConverters\MultipleMediaPickerPropertyConverter.cs" />
|
||||||
|
<Compile Include="PropertyEditors\ValueConverters\NestedContentSingleValueConverter.cs" />
|
||||||
|
<Compile Include="PropertyEditors\ValueConverters\NestedContentManyValueConverter.cs" />
|
||||||
|
<Compile Include="PropertyEditors\ValueConverters\NestedContentPublishedPropertyTypeExtensions.cs" />
|
||||||
<Compile Include="PublishedContentQueryExtensions.cs" />
|
<Compile Include="PublishedContentQueryExtensions.cs" />
|
||||||
<Compile Include="Routing\RedirectTrackingEventHandler.cs" />
|
<Compile Include="Routing\RedirectTrackingEventHandler.cs" />
|
||||||
<Compile Include="Editors\RedirectUrlManagementController.cs" />
|
<Compile Include="Editors\RedirectUrlManagementController.cs" />
|
||||||
@@ -470,29 +501,6 @@
|
|||||||
<Compile Include="BatchedDatabaseServerMessenger.cs" />
|
<Compile Include="BatchedDatabaseServerMessenger.cs" />
|
||||||
<Compile Include="BatchedWebServiceServerMessenger.cs" />
|
<Compile Include="BatchedWebServiceServerMessenger.cs" />
|
||||||
<Compile Include="CacheHelperExtensions.cs" />
|
<Compile Include="CacheHelperExtensions.cs" />
|
||||||
<Compile Include="Cache\ApplicationCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\ApplicationTreeCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\ContentTypeCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\DataTypeCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\DictionaryCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\DistributedCache.cs" />
|
|
||||||
<Compile Include="Cache\DistributedCacheExtensions.cs" />
|
|
||||||
<Compile Include="Cache\CacheRefresherEventHandler.cs" />
|
|
||||||
<Compile Include="Cache\DomainCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\LanguageCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\MacroCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\MediaCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\MemberCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\MemberGroupCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\PageCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\PublicAccessCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\StylesheetCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\StylesheetPropertyCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\TemplateCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\UnpublishedPageCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\UserCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\UserPermissionsCacheRefresher.cs" />
|
|
||||||
<Compile Include="Cache\UserTypeCacheRefresher.cs" />
|
|
||||||
<Compile Include="Editors\AuthenticationController.cs" />
|
<Compile Include="Editors\AuthenticationController.cs" />
|
||||||
<Compile Include="Controllers\UmbProfileController.cs" />
|
<Compile Include="Controllers\UmbProfileController.cs" />
|
||||||
<Compile Include="Editors\ContentController.cs" />
|
<Compile Include="Editors\ContentController.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user