Merge branch 'v8/8.6' up into v8/dev

# Conflicts:
#	src/SolutionInfo.cs
#	src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs
This commit is contained in:
Warren Buckley
2020-02-24 15:11:58 +00:00
18 changed files with 394 additions and 53 deletions

View File

@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2019")]
[assembly: AssemblyCopyright("Copyright © Umbraco 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@@ -27,6 +27,13 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns></returns>
bool HasContainerInPath(string contentPath);
/// <summary>
/// Gets a value indicating whether there is a list view content item in the path.
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
bool HasContainerInPath(params int[] ids);
/// <summary>
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
/// </summary>

View File

@@ -1309,14 +1309,16 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern",
return test;
}
/// <summary>
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
/// </summary>
/// <param name="contentPath"></param>
/// <returns></returns>
/// <inheritdoc />
public bool HasContainerInPath(string contentPath)
{
var ids = contentPath.Split(',').Select(int.Parse);
var ids = contentPath.Split(',').Select(int.Parse).ToArray();
return HasContainerInPath(ids);
}
/// <inheritdoc />
public bool HasContainerInPath(params int[] ids)
{
var sql = new Sql($@"SELECT COUNT(*) FROM cmsContentType
INNER JOIN {Constants.DatabaseSchema.Tables.Content} ON cmsContentType.nodeId={Constants.DatabaseSchema.Tables.Content}.contentTypeId
WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", new { ids, isContainer = true });

View File

@@ -13,35 +13,46 @@ namespace Umbraco.Core.PropertyEditors
public IEnumerable<UmbracoEntityReference> GetAllReferences(PropertyCollection properties, PropertyEditorCollection propertyEditors)
{
var trackedRelations = new List<UmbracoEntityReference>();
var trackedRelations = new HashSet<UmbracoEntityReference>();
foreach (var p in properties)
{
if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue;
//TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here
if (!p.PropertyType.VariesByNothing()) continue;
var val = p.GetValue(); // get the invariant value
//TODO: We will need to change this once we support tracking via variants/segments
// for now, we are tracking values from ALL variants
var valueEditor = editor.GetValueEditor();
if (valueEditor is IDataValueReference reference)
foreach(var propertyVal in p.Values)
{
var refs = reference.GetReferences(val);
trackedRelations.AddRange(refs);
}
var val = propertyVal.EditedValue;
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (var item in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
trackedRelations.AddRange(item.GetDataValueReference().GetReferences(val));
var valueEditor = editor.GetValueEditor();
if (valueEditor is IDataValueReference reference)
{
var refs = reference.GetReferences(val);
foreach(var r in refs)
trackedRelations.Add(r);
}
// Loop over collection that may be add to existing property editors
// implementation of GetReferences in IDataValueReference.
// Allows developers to add support for references by a
// package /property editor that did not implement IDataValueReference themselves
foreach (var item in this)
{
// Check if this value reference is for this datatype/editor
// Then call it's GetReferences method - to see if the value stored
// in the dataeditor/property has referecnes to media/content items
if (item.IsForEditor(editor))
{
foreach(var r in item.GetDataValueReference().GetReferences(val))
trackedRelations.Add(r);
}
}
}
}
return trackedRelations;

View File

@@ -69,6 +69,13 @@ namespace Umbraco.Core.Services
/// <returns></returns>
bool HasContainerInPath(string contentPath);
/// <summary>
/// Gets a value indicating whether there is a list view content item in the path.
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
bool HasContainerInPath(params int[] ids);
Attempt<OperationResult<OperationResultType, EntityContainer>> CreateContainer(int parentContainerId, string name, int userId = Constants.Security.SuperUserId);
Attempt<OperationResult> SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
EntityContainer GetContainer(int containerId);

View File

@@ -321,6 +321,15 @@ namespace Umbraco.Core.Services.Implement
}
}
public bool HasContainerInPath(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
// can use same repo for both content and media
return Repository.HasContainerInPath(ids);
}
}
public IEnumerable<TItem> GetDescendants(int id, bool andSelf)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))

View File

@@ -0,0 +1,223 @@
using Moq;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.PropertyEditors;
using static Umbraco.Core.Models.Property;
namespace Umbraco.Tests.PropertyEditors
{
[TestFixture]
public class DataValueReferenceFactoryCollectionTests
{
[Test]
public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory()
{
var collection = new DataValueReferenceFactoryCollection(new TestDataValueReferenceFactory().Yield());
// label does not implement IDataValueReference
var labelEditor = new LabelPropertyEditor(Mock.Of<ILogger>());
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(labelEditor.Yield()));
var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var property = new Property(new PropertyType(new DataType(labelEditor))
{
Variations = ContentVariation.CultureAndSegment
})
{
Values = new List<PropertyValue>
{
// Ignored (no culture)
new PropertyValue
{
EditedValue = trackedUdi1
},
new PropertyValue
{
Culture = "en-US",
EditedValue = trackedUdi2
},
new PropertyValue
{
Culture = "en-US",
Segment = "A",
EditedValue = trackedUdi3
},
// Ignored (no culture)
new PropertyValue
{
Segment = "A",
EditedValue = trackedUdi4
},
// duplicate
new PropertyValue
{
Culture = "en-US",
Segment = "B",
EditedValue = trackedUdi3
}
}
};
var properties = new PropertyCollection
{
property
};
var result = collection.GetAllReferences(properties, propertyEditors);
Assert.AreEqual(2, result.Count());
Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString());
Assert.AreEqual(trackedUdi3, result.ElementAt(1).Udi.ToString());
}
[Test]
public void GetAllReferences_All_Variants_With_IDataValueReference_Editor()
{
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
// mediaPicker does implement IDataValueReference
var mediaPicker = new MediaPickerPropertyEditor(Mock.Of<ILogger>());
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield()));
var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var property = new Property(new PropertyType(new DataType(mediaPicker))
{
Variations = ContentVariation.CultureAndSegment
})
{
Values = new List<PropertyValue>
{
// Ignored (no culture)
new PropertyValue
{
EditedValue = trackedUdi1
},
new PropertyValue
{
Culture = "en-US",
EditedValue = trackedUdi2
},
new PropertyValue
{
Culture = "en-US",
Segment = "A",
EditedValue = trackedUdi3
},
// Ignored (no culture)
new PropertyValue
{
Segment = "A",
EditedValue = trackedUdi4
},
// duplicate
new PropertyValue
{
Culture = "en-US",
Segment = "B",
EditedValue = trackedUdi3
}
}
};
var properties = new PropertyCollection
{
property
};
var result = collection.GetAllReferences(properties, propertyEditors);
Assert.AreEqual(2, result.Count());
Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString());
Assert.AreEqual(trackedUdi3, result.ElementAt(1).Udi.ToString());
}
[Test]
public void GetAllReferences_Invariant_With_IDataValueReference_Editor()
{
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
// mediaPicker does implement IDataValueReference
var mediaPicker = new MediaPickerPropertyEditor(Mock.Of<ILogger>());
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield()));
var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi3 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var trackedUdi4 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString();
var property = new Property(new PropertyType(new DataType(mediaPicker))
{
Variations = ContentVariation.Nothing | ContentVariation.Segment
})
{
Values = new List<PropertyValue>
{
new PropertyValue
{
EditedValue = trackedUdi1
},
// Ignored (has culture)
new PropertyValue
{
Culture = "en-US",
EditedValue = trackedUdi2
},
// Ignored (has culture)
new PropertyValue
{
Culture = "en-US",
Segment = "A",
EditedValue = trackedUdi3
},
new PropertyValue
{
Segment = "A",
EditedValue = trackedUdi4
},
// duplicate
new PropertyValue
{
Segment = "B",
EditedValue = trackedUdi4
}
}
};
var properties = new PropertyCollection
{
property
};
var result = collection.GetAllReferences(properties, propertyEditors);
Assert.AreEqual(2, result.Count());
Assert.AreEqual(trackedUdi1, result.ElementAt(0).Udi.ToString());
Assert.AreEqual(trackedUdi4, result.ElementAt(1).Udi.ToString());
}
private class TestDataValueReferenceFactory : IDataValueReferenceFactory
{
public IDataValueReference GetDataValueReference() => new TestMediaDataValueReference();
public bool IsForEditor(IDataEditor dataEditor) => dataEditor.Alias == Constants.PropertyEditors.Aliases.Label;
private class TestMediaDataValueReference : IDataValueReference
{
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
{
// This is the same as the media picker, it will just try to parse the value directly as a UDI
var asString = value is string str ? str : value?.ToString();
if (string.IsNullOrEmpty(asString)) yield break;
if (Udi.TryParse(asString, out var udi))
yield return new UmbracoEntityReference(udi);
}
}
}
}
}

View File

@@ -145,6 +145,7 @@
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 581 KiB

View File

@@ -9,6 +9,7 @@
ng-model="vm.model"
ng-change="vm.onChange()"
ng-keydown="vm.onKeyDown($event)"
ng-blur="vm.onBlur($event)"
prevent-enter-submit
no-dirty-check>
</ng-form>

View File

@@ -10,7 +10,8 @@
bindings: {
model: "=",
onStartTyping: "&?",
onSearch: "&?"
onSearch: "&?",
onBlur: "&?"
}
});

View File

@@ -147,7 +147,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
_.each($scope.model.value, function (item){
// we must reload the "document" link URLs to match the current editor culture
if (item.udi.indexOf("/document/") > 0) {
if (item.udi && item.udi.indexOf("/document/") > 0) {
item.url = null;
entityResource.getUrlByUdi(item.udi).then(function (data) {
item.url = data;

View File

@@ -14,7 +14,7 @@
vm.userStates = [];
vm.selection = [];
vm.newUser = {};
vm.usersOptions = {filter:null};
vm.usersOptions = {};
vm.userSortData = [
{ label: "Name (A-Z)", key: "Name", direction: "Ascending" },
{ label: "Name (Z-A)", key: "Name", direction: "Descending" },
@@ -112,6 +112,7 @@
vm.selectAll = selectAll;
vm.areAllSelected = areAllSelected;
vm.searchUsers = searchUsers;
vm.onBlurSearch = onBlurSearch;
vm.getFilterName = getFilterName;
vm.setUserStatesFilter = setUserStatesFilter;
vm.setUserGroupFilter = setUserGroupFilter;
@@ -150,10 +151,12 @@
function initViewOptions() {
// Start with default view options.
vm.usersOptions.filter = "";
vm.usersOptions.orderBy = "Name";
vm.usersOptions.orderDirection = "Ascending";
// Update from querystring if available.
initViewOptionFromQueryString("filter");
initViewOptionFromQueryString("orderBy");
initViewOptionFromQueryString("orderDirection");
initViewOptionFromQueryString("pageNumber");
@@ -451,7 +454,8 @@
var search = _.debounce(function () {
$scope.$apply(function () {
changePageNumber(1);
vm.usersOptions.pageNumber = 1;
getUsers();
});
}, 500);
@@ -459,6 +463,10 @@
search();
}
function onBlurSearch() {
updateLocation("filter", vm.usersOptions.filter);
}
function getFilterName(array) {
var name = vm.labels.all;
var found = false;
@@ -547,6 +555,7 @@
}
function updateLocation(key, value) {
$location.search("filter", vm.usersOptions.filter);// update filter, but first when something else requests a url update.
$location.search(key, value);
}
@@ -657,7 +666,8 @@
function usersOptionsAsQueryString() {
var qs = "?orderBy=" + vm.usersOptions.orderBy +
"&orderDirection=" + vm.usersOptions.orderDirection +
"&pageNumber=" + vm.usersOptions.pageNumber;
"&pageNumber=" + vm.usersOptions.pageNumber +
"&filter=" + vm.usersOptions.filter;
qs += addUsersOptionsFilterCollectionToQueryString("userStates", vm.usersOptions.userStates);
qs += addUsersOptionsFilterCollectionToQueryString("userGroups", vm.usersOptions.userGroups);

View File

@@ -28,7 +28,7 @@
</umb-editor-sub-header-section>
<umb-editor-sub-header-section>
<umb-mini-search model="vm.usersOptions.filter" on-search="vm.searchUsers()" on-start-typing="vm.searchUsers()">
<umb-mini-search model="vm.usersOptions.filter" on-search="vm.searchUsers()" on-blur="vm.onBlurSearch()">
</umb-mini-search>
</umb-editor-sub-header-section>

View File

@@ -869,7 +869,7 @@ namespace Umbraco.Web.Editors
return true;
}
/// <summary>
/// Helper method to perform the saving of the content and add the notifications to the result
@@ -1161,14 +1161,14 @@ namespace Umbraco.Web.Editors
//validate if we can publish based on the mandatory language requirements
var canPublish = ValidatePublishingMandatoryLanguages(
cultureErrors,
contentItem, cultureVariants, mandatoryCultures,
contentItem, cultureVariants, mandatoryCultures,
mandatoryVariant => mandatoryVariant.Publish);
//Now check if there are validation errors on each variant.
//If validation errors are detected on a variant and it's state is set to 'publish', then we
//need to change it to 'save'.
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
foreach (var variant in contentItem.Variants)
{
if (cultureErrors.Contains(variant.Culture))
@@ -1656,14 +1656,14 @@ namespace Umbraco.Web.Editors
[HttpPost]
public DomainSave PostSaveLanguageAndDomains(DomainSave model)
{
foreach(var domain in model.Domains)
foreach (var domain in model.Domains)
{
try
{
var uri = DomainUtilities.ParseUriFromDomainName(domain.Name, Request.RequestUri);
}
catch (UriFormatException)
{
{
var response = Request.CreateValidationErrorResponse(Services.TextService.Localize("assignDomain/invalidDomain"));
throw new HttpResponseException(response);
}
@@ -1829,7 +1829,7 @@ namespace Umbraco.Web.Editors
base.HandleInvalidModelState(display);
}
/// <summary>
/// Maps the dto property values and names to the persisted model
/// </summary>
@@ -1842,7 +1842,7 @@ namespace Umbraco.Web.Editors
var culture = property.PropertyType.VariesByCulture() ? variant.Culture : null;
var segment = property.PropertyType.VariesBySegment() ? variant.Segment : null;
return (culture, segment);
}
}
var variantIndex = 0;
@@ -1884,15 +1884,15 @@ namespace Umbraco.Web.Editors
(save, property) =>
{
// Get property value
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
return property.GetValue(culture, segment);
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
return property.GetValue(culture, segment);
},
(save, property, v) =>
{
// Set property value
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
property.SetValue(v, culture, segment);
},
property.SetValue(v, culture, segment);
},
variant.Culture);
variantIndex++;
@@ -2172,7 +2172,10 @@ namespace Umbraco.Web.Editors
/// <returns></returns>
private ContentItemDisplay MapToDisplay(IContent content)
{
var display = Mapper.Map<ContentItemDisplay>(content);
var display = Mapper.Map<ContentItemDisplay>(content, context =>
{
context.Items["CurrentUser"] = Security.CurrentUser;
});
display.AllowPreview = display.AllowPreview && content.Trashed == false && content.ContentType.IsElement == false;
return display;
}

View File

@@ -5,6 +5,7 @@ using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Routing;
@@ -27,6 +28,7 @@ namespace Umbraco.Web.Models.Mapping
private readonly ILocalizationService _localizationService;
private readonly ILogger _logger;
private readonly IUserService _userService;
private readonly IEntityService _entityService;
private readonly TabsAndPropertiesMapper<IContent> _tabsAndPropertiesMapper;
private readonly ContentSavedStateMapper<ContentPropertyDisplay> _stateMapper;
private readonly ContentBasicSavedStateMapper<ContentPropertyBasic> _basicStateMapper;
@@ -34,7 +36,7 @@ namespace Umbraco.Web.Models.Mapping
public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService,
IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger,
IUserService userService)
IUserService userService, IEntityService entityService)
{
_commonMapper = commonMapper;
_localizedTextService = localizedTextService;
@@ -46,7 +48,7 @@ namespace Umbraco.Web.Models.Mapping
_localizationService = localizationService;
_logger = logger;
_userService = userService;
_entityService = entityService;
_tabsAndPropertiesMapper = new TabsAndPropertiesMapper<IContent>(localizedTextService);
_stateMapper = new ContentSavedStateMapper<ContentPropertyDisplay>();
_basicStateMapper = new ContentBasicSavedStateMapper<ContentPropertyBasic>();
@@ -80,7 +82,7 @@ namespace Umbraco.Web.Models.Mapping
target.Icon = source.ContentType.Icon;
target.Id = source.Id;
target.IsBlueprint = source.Blueprint;
target.IsChildOfListView = DetermineIsChildOfListView(source);
target.IsChildOfListView = DetermineIsChildOfListView(source, context);
target.IsContainer = source.ContentType.IsContainer;
target.IsElement = source.ContentType.IsElement;
target.Key = source.Key;
@@ -211,13 +213,66 @@ namespace Umbraco.Web.Models.Mapping
return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})";
}
private bool DetermineIsChildOfListView(IContent source)
/// <summary>
/// Checks if the content item is a descendant of a list view
/// </summary>
/// <param name="source"></param>
/// <param name="context"></param>
/// <returns>
/// Returns true if the content item is a descendant of a list view and where the content is
/// not a current user's start node.
/// </returns>
/// <remarks>
/// We must check if it's the current user's start node because in that case we will actually be
/// rendering the tree node underneath the list view to visually show context. In this case we return
/// false because the item is technically not being rendered as part of a list view but instead as a
/// real tree node. If we didn't perform this check then tree syncing wouldn't work correctly.
/// </remarks>
private bool DetermineIsChildOfListView(IContent source, MapperContext context)
{
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var userStartNodes = Array.Empty<int>();
// In cases where a user's start node is below a list view, we will actually render
// out the tree to that start node and in that case for that start node, we want to return
// false here.
if (context.HasItems && context.Items.TryGetValue("CurrentUser", out var usr) && usr is IUser currentUser)
{
userStartNodes = currentUser.CalculateContentStartNodeIds(_entityService);
if (!userStartNodes.Contains(Constants.System.Root))
{
// return false if this is the user's actual start node, the node will be rendered in the tree
// regardless of if it's a list view or not
if (userStartNodes.Contains(source.Id))
return false;
}
}
var parent = _contentService.GetParent(source);
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
if (parent == null)
return false;
var pathParts = parent.Path.Split(',').Select(x => int.TryParse(x, out var i) ? i : 0).ToList();
// reduce the path parts so we exclude top level content items that
// are higher up than a user's start nodes
foreach (var n in userStartNodes)
{
var index = pathParts.IndexOf(n);
if (index != -1)
{
// now trim all top level start nodes to the found index
for (var i = 0; i < index; i++)
{
pathParts.RemoveAt(0);
}
}
}
return parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(pathParts.ToArray());
}
private DateTime? GetScheduledDate(IContent source, ContentScheduleAction action, MapperContext context)
{
var culture = context.GetCulture() ?? string.Empty;

View File

@@ -15,6 +15,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
private readonly HtmlLocalLinkParser _localLinkParser;
private readonly HtmlUrlParser _urlParser;
[Obsolete("Use ctor defining all dependencies instead")]
public MarkdownEditorValueConverter()
: this(Current.Factory.GetInstance<HtmlLocalLinkParser>(), Current.Factory.GetInstance<HtmlUrlParser>())
{
}
public MarkdownEditorValueConverter(HtmlLocalLinkParser localLinkParser, HtmlUrlParser urlParser)
{
_localLinkParser = localLinkParser;
@@ -25,7 +31,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
=> Constants.PropertyEditors.Aliases.MarkdownEditor == propertyType.EditorAlias;
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
=> typeof (IHtmlString);
=> typeof(IHtmlString);
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
=> PropertyCacheLevel.Snapshot;

View File

@@ -364,7 +364,12 @@ namespace Umbraco.Web.Trees
var startNodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes);
//if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell
// the UI that this node does have children and that it isn't a container
if (startNodes.Any(x => x.ParentId == e.Id))
if (startNodes.Any(x =>
{
var pathParts = x.Path.Split(',');
return pathParts.Contains(e.Id.ToInvariantString());
}))
{
renderChildren = true;
}