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>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
if (_index.HasValue) return _index.Value;
|
||||
|
||||
// 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
|
||||
var index = _contentSet.FindIndex(x => x.Id == Id);
|
||||
@@ -147,7 +147,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
|
||||
public override IEnumerable<IPublishedContent> ContentSet
|
||||
{
|
||||
get { return _contentSet ?? Content.ContentSet; }
|
||||
get { return _contentSet ?? WrappedContentInternal.ContentSet; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -161,8 +161,8 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
get
|
||||
{
|
||||
return _properties == null
|
||||
? Content.Properties
|
||||
: Content.Properties.Union(_properties).ToList();
|
||||
? WrappedContentInternal.Properties
|
||||
: WrappedContentInternal.Properties.Union(_properties).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,15 +175,15 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
var property = _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias));
|
||||
if (property != null) return property.HasValue ? property.Value : null;
|
||||
}
|
||||
return Content[alias];
|
||||
return WrappedContentInternal[alias];
|
||||
}
|
||||
}
|
||||
|
||||
public override IPublishedProperty GetProperty(string alias)
|
||||
{
|
||||
return _properties == null
|
||||
? Content.GetProperty(alias)
|
||||
: _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias)) ?? Content.GetProperty(alias);
|
||||
? WrappedContentInternal.GetProperty(alias)
|
||||
: _properties.FirstOrDefault(prop => prop.PropertyTypeAlias.InvariantEquals(alias)) ?? WrappedContentInternal.GetProperty(alias);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
: 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)
|
||||
{ }
|
||||
|
||||
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)
|
||||
{ }
|
||||
|
||||
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>
|
||||
public abstract class PublishedContentWrapped : IPublishedContent
|
||||
{
|
||||
protected readonly IPublishedContent Content;
|
||||
protected readonly IPublishedContent WrappedContentInternal;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
protected PublishedContentWrapped(IPublishedContent content)
|
||||
{
|
||||
Content = content;
|
||||
WrappedContentInternal = content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,21 +45,21 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
/// <returns>The wrapped content, that was passed as an argument to the constructor.</returns>
|
||||
public IPublishedContent Unwrap()
|
||||
{
|
||||
return Content;
|
||||
return WrappedContentInternal;
|
||||
}
|
||||
|
||||
#region ContentSet
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> ContentSet
|
||||
{
|
||||
get { return Content.ContentSet; }
|
||||
get { return WrappedContentInternal.ContentSet; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ContentType
|
||||
|
||||
public virtual PublishedContentType ContentType { get { return Content.ContentType; } }
|
||||
public virtual PublishedContentType ContentType { get { return WrappedContentInternal.ContentType; } }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -67,102 +67,102 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
|
||||
public virtual int Id
|
||||
{
|
||||
get { return Content.Id; }
|
||||
get { return WrappedContentInternal.Id; }
|
||||
}
|
||||
|
||||
public virtual int TemplateId
|
||||
{
|
||||
get { return Content.TemplateId; }
|
||||
get { return WrappedContentInternal.TemplateId; }
|
||||
}
|
||||
|
||||
public virtual int SortOrder
|
||||
{
|
||||
get { return Content.SortOrder; }
|
||||
get { return WrappedContentInternal.SortOrder; }
|
||||
}
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get { return Content.Name; }
|
||||
get { return WrappedContentInternal.Name; }
|
||||
}
|
||||
|
||||
public virtual string UrlName
|
||||
{
|
||||
get { return Content.UrlName; }
|
||||
get { return WrappedContentInternal.UrlName; }
|
||||
}
|
||||
|
||||
public virtual string DocumentTypeAlias
|
||||
{
|
||||
get { return Content.DocumentTypeAlias; }
|
||||
get { return WrappedContentInternal.DocumentTypeAlias; }
|
||||
}
|
||||
|
||||
public virtual int DocumentTypeId
|
||||
{
|
||||
get { return Content.DocumentTypeId; }
|
||||
get { return WrappedContentInternal.DocumentTypeId; }
|
||||
}
|
||||
|
||||
public virtual string WriterName
|
||||
{
|
||||
get { return Content.WriterName; }
|
||||
get { return WrappedContentInternal.WriterName; }
|
||||
}
|
||||
|
||||
public virtual string CreatorName
|
||||
{
|
||||
get { return Content.CreatorName; }
|
||||
get { return WrappedContentInternal.CreatorName; }
|
||||
}
|
||||
|
||||
public virtual int WriterId
|
||||
{
|
||||
get { return Content.WriterId; }
|
||||
get { return WrappedContentInternal.WriterId; }
|
||||
}
|
||||
|
||||
public virtual int CreatorId
|
||||
{
|
||||
get { return Content.CreatorId; }
|
||||
get { return WrappedContentInternal.CreatorId; }
|
||||
}
|
||||
|
||||
public virtual string Path
|
||||
{
|
||||
get { return Content.Path; }
|
||||
get { return WrappedContentInternal.Path; }
|
||||
}
|
||||
|
||||
public virtual DateTime CreateDate
|
||||
{
|
||||
get { return Content.CreateDate; }
|
||||
get { return WrappedContentInternal.CreateDate; }
|
||||
}
|
||||
|
||||
public virtual DateTime UpdateDate
|
||||
{
|
||||
get { return Content.UpdateDate; }
|
||||
get { return WrappedContentInternal.UpdateDate; }
|
||||
}
|
||||
|
||||
public virtual Guid Version
|
||||
{
|
||||
get { return Content.Version; }
|
||||
get { return WrappedContentInternal.Version; }
|
||||
}
|
||||
|
||||
public virtual int Level
|
||||
{
|
||||
get { return Content.Level; }
|
||||
get { return WrappedContentInternal.Level; }
|
||||
}
|
||||
|
||||
public virtual string Url
|
||||
{
|
||||
get { return Content.Url; }
|
||||
get { return WrappedContentInternal.Url; }
|
||||
}
|
||||
|
||||
public virtual PublishedItemType ItemType
|
||||
{
|
||||
get { return Content.ItemType; }
|
||||
get { return WrappedContentInternal.ItemType; }
|
||||
}
|
||||
|
||||
public virtual bool IsDraft
|
||||
{
|
||||
get { return Content.IsDraft; }
|
||||
get { return WrappedContentInternal.IsDraft; }
|
||||
}
|
||||
|
||||
public virtual int GetIndex()
|
||||
{
|
||||
return Content.GetIndex();
|
||||
return WrappedContentInternal.GetIndex();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -171,12 +171,12 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
|
||||
public virtual IPublishedContent Parent
|
||||
{
|
||||
get { return Content.Parent; }
|
||||
get { return WrappedContentInternal.Parent; }
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> Children
|
||||
{
|
||||
get { return Content.Children; }
|
||||
get { return WrappedContentInternal.Children; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -185,22 +185,22 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
|
||||
public virtual ICollection<IPublishedProperty> Properties
|
||||
{
|
||||
get { return Content.Properties; }
|
||||
get { return WrappedContentInternal.Properties; }
|
||||
}
|
||||
|
||||
public virtual object this[string alias]
|
||||
{
|
||||
get { return Content[alias]; }
|
||||
get { return WrappedContentInternal[alias]; }
|
||||
}
|
||||
|
||||
public virtual IPublishedProperty GetProperty(string alias)
|
||||
{
|
||||
return Content.GetProperty(alias);
|
||||
return WrappedContentInternal.GetProperty(alias);
|
||||
}
|
||||
|
||||
public virtual IPublishedProperty GetProperty(string alias, bool recurse)
|
||||
{
|
||||
return Content.GetProperty(alias, recurse);
|
||||
return WrappedContentInternal.GetProperty(alias, recurse);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -42,7 +42,16 @@ namespace Umbraco.Core.PropertyEditors
|
||||
internal PropertyEditorResolver(IServiceProvider serviceProvider, ILogger logger, Func<IEnumerable<Type>> typeListProducerList, ManifestBuilder builder)
|
||||
: 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;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
||||
|
||||
protected T Resolve<T>(string propertyTypeAlias)
|
||||
{
|
||||
return Content.GetPropertyValue<T>(propertyTypeAlias);
|
||||
return WrappedContentInternal.GetPropertyValue<T>(propertyTypeAlias);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return Content.GetPropertyValue<T>(propertyTypeAlias, false, ifCannotConvert);
|
||||
return WrappedContentInternal.GetPropertyValue<T>(propertyTypeAlias, false, 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)
|
||||
{
|
||||
return Content.GetPropertyValue<T>(propertyTypeAlias, recursive, ifCannotConvert);
|
||||
return WrappedContentInternal.GetPropertyValue<T>(propertyTypeAlias, recursive, ifCannotConvert);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
||||
if (constructorInfo == null)
|
||||
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
|
||||
@@ -98,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
||||
|
||||
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 }));
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
||||
|
||||
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 }));
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels
|
||||
|
||||
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 }));
|
||||
}
|
||||
#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-pagination.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-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.PublishedContent;
|
||||
using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Web.PropertyEditors;
|
||||
using Umbraco.Web.PropertyEditors.ValueConverters;
|
||||
|
||||
|
||||
@@ -13,12 +14,12 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// A cache refresher to ensure member cache is updated when members change
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
public sealed class DataTypeCacheRefresher : JsonCacheRefresherBase<DataTypeCacheRefresher>
|
||||
{
|
||||
|
||||
#region Static helpers
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts the json to a JsonPayload object
|
||||
/// </summary>
|
||||
@@ -29,7 +30,7 @@ namespace Umbraco.Web.Cache
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a macro to a jsonPayload object
|
||||
/// </summary>
|
||||
@@ -58,7 +59,7 @@ namespace Umbraco.Web.Cache
|
||||
};
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#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
|
||||
// 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
|
||||
|
||||
|
||||
ClearAllIsolatedCacheByEntityType<IContent>();
|
||||
ClearAllIsolatedCacheByEntityType<IContentType>();
|
||||
ClearAllIsolatedCacheByEntityType<IMedia>();
|
||||
@@ -104,14 +105,15 @@ namespace Umbraco.Web.Cache
|
||||
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey);
|
||||
|
||||
var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache<IDataTypeDefinition>();
|
||||
payloads.ForEach(payload =>
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
//clears the prevalue cache
|
||||
if (dataTypeCache)
|
||||
dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}_{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id));
|
||||
|
||||
PublishedContentType.ClearDataType(payload.Id);
|
||||
});
|
||||
NestedContentHelper.ClearCache(payload.Id);
|
||||
}
|
||||
|
||||
TagsValueConverter.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)
|
||||
{
|
||||
var wrapped = content as PublishedContentWrapped;
|
||||
while (wrapped != null)
|
||||
{
|
||||
content = wrapped.Unwrap();
|
||||
wrapped = content as PublishedContentWrapped;
|
||||
}
|
||||
var contentWithKey = content as IPublishedContentWithKey;
|
||||
return contentWithKey == null ? Guid.Empty : contentWithKey.Key;
|
||||
}
|
||||
|
||||
@@ -304,7 +304,30 @@
|
||||
<Compile Include="ApplicationContextExtensions.cs" />
|
||||
<Compile Include="AreaRegistrationContextExtensions.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\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\EditorValidationResolver.cs" />
|
||||
<Compile Include="Editors\EditorValidator.cs" />
|
||||
@@ -366,6 +389,8 @@
|
||||
<Compile Include="Models\ContentEditing\SimpleNotificationModel.cs" />
|
||||
<Compile Include="Models\ContentEditing\SnippetDisplay.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\Mapping\CodeFileDisplayMapper.cs" />
|
||||
<Compile Include="Models\Mapping\ContentTypeModelMapperExtensions.cs" />
|
||||
@@ -402,6 +427,9 @@
|
||||
<Compile Include="PropertyEditors\MediaPicker2PropertyEditor.cs" />
|
||||
<Compile Include="PropertyEditors\MemberPicker2PropertyEditor.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\ValueConverters\ContentPickerPropertyConverter.cs" />
|
||||
<Compile Include="PropertyEditors\ParameterEditors\MultipleContentPickerParameterEditor.cs" />
|
||||
@@ -412,6 +440,9 @@
|
||||
<Compile Include="PropertyEditors\ValueConverters\MemberPickerPropertyConverter.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\MultiNodeTreePickerPropertyConverter.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="Routing\RedirectTrackingEventHandler.cs" />
|
||||
<Compile Include="Editors\RedirectUrlManagementController.cs" />
|
||||
@@ -470,29 +501,6 @@
|
||||
<Compile Include="BatchedDatabaseServerMessenger.cs" />
|
||||
<Compile Include="BatchedWebServiceServerMessenger.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="Controllers\UmbProfileController.cs" />
|
||||
<Compile Include="Editors\ContentController.cs" />
|
||||
|
||||
Reference in New Issue
Block a user