Merge branch 'dev-v7.7' into temp-content-blueprints

This commit is contained in:
Shannon
2017-06-05 10:23:10 +02:00
31 changed files with 2022 additions and 101 deletions
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.6.3");
private static readonly Version Version = new Version("7.6.4");
/// <summary>
/// Gets the current version of Umbraco.
@@ -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'
);
},
};
});
@@ -17,7 +17,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
//add to umbraco installer facts here
var facts = ['Umbraco helped millions of people watch a man jump from the edge of space',
'Over 370 000 websites are currently powered by Umbraco',
'Over 420 000 websites are currently powered by Umbraco',
"At least 2 people have named their cat 'Umbraco'",
'On an average day, more than 1000 people download Umbraco',
'<a target="_blank" href="http://umbraco.tv">umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started',
@@ -31,7 +31,7 @@ angular.module("umbraco.install").factory('installerService', function($rootScop
"At least 4 people have the Umbraco logo tattooed on them",
"'Umbraco' is the danish name for an allen key",
"Umbraco has been around since 2005, that's a looong time in IT",
"More than 400 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden15.com'>CodeGarden</a>",
"More than 550 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden15.com'>CodeGarden</a>",
"While you are installing Umbraco someone else on the other side of the planet is probably doing it too",
"You can extend Umbraco without modifying the source code using either JavaScript or C#",
"Umbraco was installed in more than 165 countries in 2015"
@@ -1,23 +1,16 @@
<div ng-controller="Umbraco.Installer.PackagesController" id="starterKits">
<h1>Install a starter website</h1>
<p>
Installing a starter website helps you learn how Umbraco works, and gives you a solid
and simple foundation to build on top of.
</p>
<small ng-if="!packages || packages.length == 0">Loading...</small>
<ul class="thumbnails">
<li class="span3" ng-repeat="pck in packages">
<a href ng-click="setPackageAndContinue(pck.id)" class="thumbnail">
<small>Loading...</small>
<img ng-src="http://our.umbraco.org{{pck.thumbnail}}?width=170" alt="{{pck.name}}">
</a>
</li>
</ul>
<h1>Would you like to learn or demo Umbraco?</h1>
<img ng-src="http://our.umbraco.org{{packages[0].thumbnail}}?width=350" style="float: left; padding: 0 15px 50px 0" alt="{{pck.name}}">
<p>The Starter Kit is a great way to experience some of the ways you can use Umbraco. It's a complete website with textpages, landing pages, blog, product listings and more that's easy to get started with Umbraco.
</p>
<p>
It's also a great way to learn Umbraco as the Starter Kit comes with a set of Lessons that'll teach you how to implement and extend Umbraco using short 5-15 minute tasks.
</p>
<p>
<a href ng-click="setPackageAndContinue(packages[0].id)" class="btn btn-success">Yes, I'd like a Starter Kit</a>
&nbsp;
<a href ng-click="setPackageAndContinue('00000000-0000-0000-0000-000000000000')" class="btn btn-link btn-link-reverse">
No thanks, I do not want to install a starter website
No thanks
</a>
</div>
@@ -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();
@@ -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; }
}
}
}
@@ -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; } }
}
}
@@ -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()
});
}
}
}
@@ -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
}
}
@@ -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;
}
+31 -23
View File
@@ -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" />
@@ -471,29 +502,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" />