Merge remote-tracking branch 'origin/netcore/dev' into netcore/netcore
This commit is contained in:
@@ -32,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
|
||||
public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
|
||||
|
||||
public NestedContentPropertyEditor(
|
||||
ILogger logger,
|
||||
|
||||
@@ -9,15 +9,28 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
/// </summary>
|
||||
internal class ContentNestedData
|
||||
{
|
||||
[JsonProperty("properties")]
|
||||
//dont serialize empty properties
|
||||
[JsonProperty("pd")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<PropertyData[]>))]
|
||||
public Dictionary<string, PropertyData[]> PropertyData { get; set; }
|
||||
|
||||
[JsonProperty("cultureData")]
|
||||
[JsonProperty("cd")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<CultureVariation>))]
|
||||
public Dictionary<string, CultureVariation> CultureData { get; set; }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
[JsonProperty("us")]
|
||||
public string UrlSegment { get; set; }
|
||||
|
||||
//Legacy properties used to deserialize existing nucache db entries
|
||||
[JsonProperty("properties")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<PropertyData[]>))]
|
||||
private Dictionary<string, PropertyData[]> LegacyPropertyData { set { PropertyData = value; } }
|
||||
|
||||
[JsonProperty("cultureData")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<CultureVariation>))]
|
||||
private Dictionary<string, CultureVariation> LegacyCultureData { set { CultureData = value; } }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
private string LegacyUrlSegment { set { UrlSegment = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,29 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
/// </summary>
|
||||
public class CultureVariation
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
[JsonProperty("nm")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
[JsonProperty("us")]
|
||||
public string UrlSegment { get; set; }
|
||||
|
||||
[JsonProperty("date")]
|
||||
[JsonProperty("dt")]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[JsonProperty("isDraft")]
|
||||
[JsonProperty("isd")]
|
||||
public bool IsDraft { get; set; }
|
||||
|
||||
//Legacy properties used to deserialize existing nucache db entries
|
||||
[JsonProperty("name")]
|
||||
private string LegacyName { set { Name = value; } }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
private string LegacyUrlSegment { set { UrlSegment = value; } }
|
||||
|
||||
[JsonProperty("date")]
|
||||
private DateTime LegacyDate { set { Date = value; } }
|
||||
|
||||
[JsonProperty("isDraft")]
|
||||
private bool LegacyIsDraft { set { IsDraft = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
@@ -8,21 +9,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
private string _culture;
|
||||
private string _segment;
|
||||
|
||||
[JsonProperty("culture")]
|
||||
[DefaultValue("")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "c")]
|
||||
public string Culture
|
||||
{
|
||||
get => _culture;
|
||||
set => _culture = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null
|
||||
}
|
||||
|
||||
[JsonProperty("seg")]
|
||||
[DefaultValue("")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "s")]
|
||||
public string Segment
|
||||
{
|
||||
get => _segment;
|
||||
set => _segment = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null
|
||||
}
|
||||
|
||||
[JsonProperty("val")]
|
||||
[JsonProperty("v")]
|
||||
public object Value { get; set; }
|
||||
|
||||
|
||||
//Legacy properties used to deserialize existing nucache db entries
|
||||
[JsonProperty("culture")]
|
||||
private string LegacyCulture
|
||||
{
|
||||
set => Culture = value;
|
||||
}
|
||||
|
||||
[JsonProperty("seg")]
|
||||
private string LegacySegment
|
||||
{
|
||||
set => Segment = value;
|
||||
}
|
||||
|
||||
[JsonProperty("val")]
|
||||
private object LegacyValue
|
||||
{
|
||||
set => Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ namespace Umbraco.Tests.Services
|
||||
private void AssertJsonStartsWith(int id, string expected)
|
||||
{
|
||||
var json = GetJson(id).Replace('"', '\'');
|
||||
var pos = json.IndexOf("'cultureData':", StringComparison.InvariantCultureIgnoreCase);
|
||||
json = json.Substring(0, pos + "'cultureData':".Length);
|
||||
var pos = json.IndexOf("'cd':", StringComparison.InvariantCultureIgnoreCase);
|
||||
json = json.Substring(0, pos + "'cd':".Length);
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content type to Nothing
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
@@ -623,7 +623,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content back to Culture
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
@@ -640,7 +640,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property back to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
@@ -656,7 +656,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -697,7 +697,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content type to Culture
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
@@ -713,7 +713,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
@@ -728,7 +728,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content back to Nothing
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
@@ -745,7 +745,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -783,7 +783,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property type to Nothing
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
|
||||
@@ -800,7 +800,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property back to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
@@ -816,7 +816,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch other property to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value2").Variations = ContentVariation.Culture;
|
||||
@@ -834,7 +834,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'c':'en','v':'v2'}]},'cd':");
|
||||
}
|
||||
|
||||
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
|
||||
@@ -1065,7 +1065,7 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are variant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composed.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
@@ -1073,7 +1073,7 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composed.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
@@ -1081,7 +1081,7 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is variant again, but value21 is still invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
@@ -1089,7 +1089,7 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1097,7 +1097,7 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1105,7 +1105,7 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is still invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1113,7 +1113,7 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -1178,11 +1178,11 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are variant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composed1.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
@@ -1190,11 +1190,11 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composed1.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
@@ -1202,11 +1202,11 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is variant again, but value21 is still invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composed1.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
@@ -1214,11 +1214,11 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1226,11 +1226,11 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1238,11 +1238,11 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is still invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1250,11 +1250,11 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
}
|
||||
|
||||
private void CreateFrenchAndEnglishLangs()
|
||||
|
||||
@@ -3,10 +3,16 @@
|
||||
module.exports = {
|
||||
compile: {
|
||||
build: {
|
||||
sourcemaps: false
|
||||
sourcemaps: false,
|
||||
embedtemplates: true
|
||||
},
|
||||
dev: {
|
||||
sourcemaps: true
|
||||
sourcemaps: true,
|
||||
embedtemplates: true
|
||||
},
|
||||
test: {
|
||||
sourcemaps: false,
|
||||
embedtemplates: true
|
||||
}
|
||||
},
|
||||
sources: {
|
||||
@@ -17,7 +23,7 @@ module.exports = {
|
||||
installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" },
|
||||
nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"},
|
||||
preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" },
|
||||
umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" },
|
||||
umbraco: { files: "./src/less/belle.less", watch: "./src/**/*.less", out: "umbraco.css" },
|
||||
rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" }
|
||||
},
|
||||
|
||||
|
||||
@@ -10,4 +10,14 @@ function setDevelopmentMode(cb) {
|
||||
return cb();
|
||||
};
|
||||
|
||||
module.exports = { setDevelopmentMode: setDevelopmentMode };
|
||||
function setTestMode(cb) {
|
||||
|
||||
config.compile.current = config.compile.test;
|
||||
|
||||
return cb();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setDevelopmentMode: setDevelopmentMode,
|
||||
setTestMode: setTestMode
|
||||
};
|
||||
|
||||
@@ -6,11 +6,24 @@ var karmaServer = require('karma').Server;
|
||||
* Build tests
|
||||
**************************/
|
||||
|
||||
// Karma test
|
||||
// Karma test
|
||||
function testUnit() {
|
||||
|
||||
return new karmaServer({
|
||||
configFile: __dirname + "/../../test/config/karma.conf.js"
|
||||
})
|
||||
.start();
|
||||
};
|
||||
|
||||
// Run karma test server
|
||||
function runUnitTestServer() {
|
||||
|
||||
return new karmaServer({
|
||||
configFile: __dirname + "/../../test/config/karma.conf.js",
|
||||
autoWatch: true,
|
||||
port: 9999,
|
||||
singleRun: false,
|
||||
browsers: ['ChromeDebugging'],
|
||||
keepalive: true
|
||||
})
|
||||
.start();
|
||||
@@ -24,4 +37,4 @@ function testE2e() {
|
||||
.start();
|
||||
};
|
||||
|
||||
module.exports = { testUnit: testUnit, testE2e: testE2e };
|
||||
module.exports = { testUnit: testUnit, testE2e: testE2e, runUnitTestServer: runUnitTestServer };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('../config');
|
||||
const {watch, parallel, dest, src} = require('gulp');
|
||||
const {watch, series, parallel, dest, src} = require('gulp');
|
||||
|
||||
var _ = require('lodash');
|
||||
var MergeStream = require('merge-stream');
|
||||
@@ -9,9 +9,7 @@ var MergeStream = require('merge-stream');
|
||||
var processJs = require('../util/processJs');
|
||||
var processLess = require('../util/processLess');
|
||||
|
||||
//const { less } = require('./less');
|
||||
//const { views } = require('./views');
|
||||
|
||||
var {js} = require('./js');
|
||||
|
||||
function watchTask(cb) {
|
||||
|
||||
@@ -30,24 +28,27 @@ function watchTask(cb) {
|
||||
watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { return processLess(group.files, group.out); });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Setup a watcher for all groups of view files
|
||||
var viewWatcher;
|
||||
_.forEach(config.sources.views, function (group) {
|
||||
if(group.watch !== false) {
|
||||
viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval });
|
||||
viewWatcher.on('change', function(path, stats) {
|
||||
|
||||
var task = src(group.files);
|
||||
viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval },
|
||||
parallel(
|
||||
function MoveViewsAndRegenerateJS() {
|
||||
var task = src(group.files);
|
||||
|
||||
_.forEach(config.roots, function(root){
|
||||
console.log("copying " + group.files + " to " + root + config.targets.views + group.folder);
|
||||
task = task.pipe( dest(root + config.targets.views + group.folder) );
|
||||
})
|
||||
});
|
||||
_.forEach(config.roots, function(root){
|
||||
console.log("copying " + group.files + " to " + root + config.targets.views + group.folder);
|
||||
task = task.pipe( dest(root + config.targets.views + group.folder) );
|
||||
});
|
||||
},
|
||||
js
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return cb();
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ module.exports = function (files, out) {
|
||||
.pipe(sort());
|
||||
|
||||
//in production, embed the templates
|
||||
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }))
|
||||
if(config.compile.current.embedtemplates === true) {
|
||||
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }));
|
||||
}
|
||||
|
||||
task = task.pipe(concat(out)).pipe(wrap('(function(){\n%= body %\n})();'))
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
const { src, dest, series, parallel, lastRun } = require('gulp');
|
||||
|
||||
const config = require('./gulp/config');
|
||||
const { setDevelopmentMode } = require('./gulp/modes');
|
||||
const { setDevelopmentMode, setTestMode } = require('./gulp/modes');
|
||||
const { dependencies } = require('./gulp/tasks/dependencies');
|
||||
const { js } = require('./gulp/tasks/js');
|
||||
const { less } = require('./gulp/tasks/less');
|
||||
const { testE2e, testUnit } = require('./gulp/tasks/test');
|
||||
const { testE2e, testUnit, runUnitTestServer } = require('./gulp/tasks/test');
|
||||
const { views } = require('./gulp/tasks/views');
|
||||
const { watchTask } = require('./gulp/tasks/watchTask');
|
||||
|
||||
@@ -31,6 +31,6 @@ exports.build = series(parallel(dependencies, js, less, views), testUnit);
|
||||
exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask);
|
||||
exports.watch = series(watchTask);
|
||||
//
|
||||
exports.runTests = series(js, testUnit);
|
||||
exports.testUnit = series(testUnit);
|
||||
exports.testE2e = series(testE2e);
|
||||
exports.runTests = series(setTestMode, parallel(js, testUnit));
|
||||
exports.runUnit = series(setTestMode, parallel(js, runUnitTestServer), watchTask);
|
||||
exports.testE2e = series(setTestMode, parallel(testE2e));
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<div ng-controller="My.Controller as vm">
|
||||
|
||||
<div class="my-gallery">
|
||||
<a href="" ng-repeat="image in images" ng-click="vm.openLightbox($index, images)">
|
||||
<button type="button" ng-repeat="image in images" ng-click="vm.openLightbox($index, images)">
|
||||
<img ng-src="image.source" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<umb-lightbox
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
*/
|
||||
function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) {
|
||||
|
||||
|
||||
var clearPropertyResolvers = [];
|
||||
|
||||
|
||||
var STORAGE_KEY = "umbClipboardService";
|
||||
|
||||
@@ -53,13 +56,32 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
return false;
|
||||
}
|
||||
|
||||
var prepareEntryForStorage = function(entryData) {
|
||||
|
||||
var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data)
|
||||
delete shallowCloneData.key;
|
||||
delete shallowCloneData.$$hashKey;
|
||||
|
||||
return shallowCloneData;
|
||||
function clearPropertyForStorage(prop) {
|
||||
|
||||
for (var i=0; i<clearPropertyResolvers.length; i++) {
|
||||
clearPropertyResolvers[i](prop, clearPropertyForStorage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var prepareEntryForStorage = function(entryData, firstLevelClearupMethod) {
|
||||
|
||||
var cloneData = Utilities.copy(entryData);
|
||||
if (firstLevelClearupMethod != undefined) {
|
||||
firstLevelClearupMethod(cloneData);
|
||||
}
|
||||
|
||||
// remove keys from sub-entries
|
||||
for (var t = 0; t < cloneData.variants[0].tabs.length; t++) {
|
||||
var tab = cloneData.variants[0].tabs[t];
|
||||
for (var p = 0; p < tab.properties.length; p++) {
|
||||
var prop = tab.properties[p];
|
||||
clearPropertyForStorage(prop);
|
||||
}
|
||||
}
|
||||
|
||||
return cloneData;
|
||||
}
|
||||
|
||||
var isEntryCompatible = function(entry, type, allowedAliases) {
|
||||
@@ -75,6 +97,38 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
|
||||
var service = {};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.clipboardService#registrerClearPropertyResolver
|
||||
* @methodOf umbraco.services.clipboardService
|
||||
*
|
||||
* @param {string} function A method executed for every property and inner properties copied.
|
||||
*
|
||||
* @description
|
||||
* Executed for all properties including inner properties when performing a copy action.
|
||||
*
|
||||
* @deprecated Incorrect spelling please use 'registerClearPropertyResolver'
|
||||
*/
|
||||
service.registrerClearPropertyResolver = function(resolver) {
|
||||
this.registerClearPropertyResolver(resolver);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.clipboardService#registerClearPropertyResolver
|
||||
* @methodOf umbraco.services.clipboardService
|
||||
*
|
||||
* @param {string} function A method executed for every property and inner properties copied.
|
||||
*
|
||||
* @description
|
||||
* Executed for all properties including inner properties when performing a copy action.
|
||||
*/
|
||||
service.registerClearPropertyResolver = function(resolver) {
|
||||
clearPropertyResolvers.push(resolver);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.clipboardService#copy
|
||||
@@ -84,15 +138,19 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
* @param {string} alias A string defining the alias of the data to store, example: 'product'
|
||||
* @param {object} entry A object containing the properties to be saved, this could be the object of a ElementType, ContentNode, ...
|
||||
* @param {string} displayLabel (optional) A string swetting the label to display when showing paste entries.
|
||||
* @param {string} displayIcon (optional) A string setting the icon to display when showing paste entries.
|
||||
* @param {string} uniqueKey (optional) A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
|
||||
*
|
||||
* @description
|
||||
* Saves a single JS-object with a type and alias to the clipboard.
|
||||
*/
|
||||
service.copy = function(type, alias, data, displayLabel) {
|
||||
service.copy = function(type, alias, data, displayLabel, displayIcon, uniqueKey, firstLevelClearupMethod) {
|
||||
|
||||
var storage = retriveStorage();
|
||||
|
||||
var uniqueKey = data.key || data.$$hashKey || console.error("missing unique key for this content");
|
||||
displayLabel = displayLabel || data.name;
|
||||
displayIcon = displayIcon || iconHelper.convertFromLegacyIcon(data.icon);
|
||||
uniqueKey = uniqueKey || data.key || console.error("missing unique key for this content");
|
||||
|
||||
// remove previous copies of this entry:
|
||||
storage.entries = storage.entries.filter(
|
||||
@@ -101,7 +159,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
}
|
||||
);
|
||||
|
||||
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel || data.name, icon:iconHelper.convertFromLegacyIcon(data.icon)};
|
||||
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon};
|
||||
storage.entries.push(entry);
|
||||
|
||||
if (saveStorage(storage) === true) {
|
||||
@@ -124,16 +182,17 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
* @param {string} displayLabel A string setting the label to display when showing paste entries.
|
||||
* @param {string} displayIcon A string setting the icon to display when showing paste entries.
|
||||
* @param {string} uniqueKey A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
|
||||
* @param {string} firstLevelClearupMethod A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
|
||||
*
|
||||
* @description
|
||||
* Saves a single JS-object with a type and alias to the clipboard.
|
||||
*/
|
||||
service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey) {
|
||||
service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey, firstLevelClearupMethod) {
|
||||
|
||||
var storage = retriveStorage();
|
||||
|
||||
// Clean up each entry
|
||||
var copiedDatas = datas.map(data => prepareEntryForStorage(data));
|
||||
var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod));
|
||||
|
||||
// remove previous copies of this entry:
|
||||
storage.entries = storage.entries.filter(
|
||||
|
||||
@@ -32,14 +32,18 @@
|
||||
.umb-lightbox__close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 60px;
|
||||
right: 20px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.umb-lightbox__close i {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.umb-lightbox__images {
|
||||
@@ -75,12 +79,20 @@
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
|
||||
.umb-lightbox__control-icon {
|
||||
margin-right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-lightbox__control.-prev {
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
|
||||
.umb-lightbox__control-icon {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-lightbox__control-icon {
|
||||
|
||||
@@ -7,18 +7,19 @@
|
||||
<div ng-if="notification.view">
|
||||
<div ng-include="notification.view"></div>
|
||||
</div>
|
||||
<div ng-if="notification.headline" ng-switch on="{{notification}}">
|
||||
<a ng-href="{{notification.url}}" ng-switch-when="{{notification.url && notification.url.trim() != ''}}" target="_blank">
|
||||
<strong>{{notification.headline}}</strong>
|
||||
|
||||
<div ng-if="notification.headline">
|
||||
<a ng-if="notification.url" ng-href="{{notification.url}}" href="" target="_blank">
|
||||
<strong ng-bind="notification.headline"></strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</a>
|
||||
<div ng-switch-default>
|
||||
<strong>{{notification.headline}}</strong>
|
||||
<div ng-if="!notification.url">
|
||||
<strong ng-bind="notification.headline"></strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class='close -align-right' ng-click="removeNotification($index)" aria-hidden="true">
|
||||
<button type="button" class="close -align-right" ng-click="removeNotification($index)" aria-hidden="true">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -7,10 +7,16 @@
|
||||
on-outside-click="clickCancel()">
|
||||
|
||||
<button class="umb_confirm-action__overlay-action -confirm btn-reset" ng-click="clickConfirm()" localize="title" title="@buttons_confirmActionConfirm" type="button">
|
||||
<i class="icon-check"></i>
|
||||
<i class="icon-check" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="buttons_confirmActionConfirm">Confirm</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="umb_confirm-action__overlay-action -cancel btn-reset" ng-click="clickCancel()" localize="title" title="@buttons_confirmActionCancel" type="button">
|
||||
<i class="icon-delete"></i>
|
||||
<i class="icon-delete" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="buttons_confirmActionCancel">Cancel</localize>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<div class="umb-lightbox">
|
||||
|
||||
<div class="umb-lightbox__backdrop" ng-click="close()" hotkey="esc"></div>
|
||||
<div class="umb-lightbox__close" title="Close" ng-click="close()">
|
||||
<i class="icon-delete umb-lightbox__control"></i>
|
||||
</div>
|
||||
<button type="button" class="btn-reset umb-lightbox__close" localize="title" title="@general_close" ng-click="close()">
|
||||
<i class="icon-delete umb-lightbox__control" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="general_close">Close</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="umb-lightbox__images">
|
||||
<div class="umb-lightbox__image shadow-depth-2" ng-repeat="item in items" ng-show="$index === activeItemIndex">
|
||||
@@ -11,12 +14,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-lightbox__control -prev" title="Previous" ng-if="activeItemIndex > 0" ng-click="prev()" hotkey="left">
|
||||
<i class="icon-previous umb-lightbox__control-icon"></i>
|
||||
</div>
|
||||
<button type="button" class="btn-reset umb-lightbox__control -prev" localize="title" title="@general_previous" ng-if="activeItemIndex > 0" ng-click="prev()" hotkey="left">
|
||||
<i class="icon-previous umb-lightbox__control-icon" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="general_previous">Previous</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="umb-lightbox__control -next" title="Next" ng-if="activeItemIndex + 1 < items.length" ng-click="next()" hotkey="right">
|
||||
<i class="icon-next umb-lightbox__control-icon"></i>
|
||||
</div>
|
||||
<button type="button" class="btn-reset umb-lightbox__control -next" localize="title" title="general_next" ng-if="activeItemIndex + 1 < items.length" ng-click="next()" hotkey="right">
|
||||
<i class="icon-next umb-lightbox__control-icon" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="general_next">Next</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,6 @@
|
||||
aria-label="Edit"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<button type="button" class="btn" ng-click="addField()">
|
||||
<localize key="general_add">Add</localize>
|
||||
</button>
|
||||
<span class="help-inline" ng-bind="errorMsg"</span>
|
||||
<span class="help-inline" ng-bind="errorMsg"></span>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<table class="table" ng-show="model.value.length > 0">
|
||||
|
||||
@@ -1,6 +1,58 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* When performing a copy, we do copy the ElementType Data Model, but each inner Nested Content property is still stored as the Nested Content Model, aka. each property is just storing its value. To handle this we need to ensure we handle both scenarios.
|
||||
*/
|
||||
|
||||
|
||||
angular.module('umbraco').run(['clipboardService', function (clipboardService) {
|
||||
|
||||
function clearNestedContentPropertiesForStorage(prop, propClearingMethod) {
|
||||
|
||||
// if prop.editor is "Umbraco.NestedContent"
|
||||
if ((typeof prop === 'object' && prop.editor === "Umbraco.NestedContent")) {
|
||||
|
||||
var value = prop.value;
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
var obj = value[i];
|
||||
|
||||
// remove the key
|
||||
delete obj.key;
|
||||
|
||||
// Loop through all inner properties:
|
||||
for (var k in obj) {
|
||||
propClearingMethod(obj[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clipboardService.registrerClearPropertyResolver(clearNestedContentPropertiesForStorage)
|
||||
|
||||
|
||||
function clearInnerNestedContentPropertiesForStorage(prop, propClearingMethod) {
|
||||
|
||||
// if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property inside a NestedContent property.
|
||||
if ((Array.isArray(prop) && prop.length > 0 && prop[0].ncContentTypeAlias !== undefined)) {
|
||||
|
||||
for (var i = 0; i < prop.length; i++) {
|
||||
var obj = prop[i];
|
||||
|
||||
// remove the key
|
||||
delete obj.key;
|
||||
|
||||
// Loop through all inner properties:
|
||||
for (var k in obj) {
|
||||
propClearingMethod(obj[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clipboardService.registrerClearPropertyResolver(clearInnerNestedContentPropertiesForStorage)
|
||||
}]);
|
||||
|
||||
angular
|
||||
.module('umbraco')
|
||||
.component('nestedContentPropertyEditor', {
|
||||
@@ -13,7 +65,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) {
|
||||
function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) {
|
||||
|
||||
var vm = this;
|
||||
var model = $scope.$parent.$parent.model;
|
||||
@@ -76,7 +128,7 @@
|
||||
}
|
||||
|
||||
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function (data) {
|
||||
clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id);
|
||||
clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id, clearNodeForCopy);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,6 +437,11 @@
|
||||
});
|
||||
}
|
||||
|
||||
function clearNodeForCopy(clonedData) {
|
||||
delete clonedData.key;
|
||||
delete clonedData.$$hashKey;
|
||||
}
|
||||
|
||||
vm.showCopy = clipboardService.isSupported();
|
||||
vm.showPaste = false;
|
||||
|
||||
@@ -392,7 +449,7 @@
|
||||
|
||||
syncCurrentNode();
|
||||
|
||||
clipboardService.copy("elementType", node.contentTypeAlias, node);
|
||||
clipboardService.copy("elementType", node.contentTypeAlias, node, null, null, null, clearNodeForCopy);
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ var app = angular.module('umbraco', [
|
||||
'ngSanitize',
|
||||
|
||||
//'ngMessages',
|
||||
'tmh.dynamicLocale'
|
||||
'tmh.dynamicLocale',
|
||||
//'ngFileUpload',
|
||||
//'LocalStorageModule',
|
||||
'LocalStorageModule'
|
||||
//'chart.js'
|
||||
]);
|
||||
|
||||
126
src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
Normal file
126
src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Web.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
public class NestedContentPropertyComponent : IComponent
|
||||
{
|
||||
public void Initialize()
|
||||
{
|
||||
ContentService.Copying += ContentService_Copying;
|
||||
ContentService.Saving += ContentService_Saving;
|
||||
}
|
||||
|
||||
private void ContentService_Copying(IContentService sender, CopyEventArgs<IContent> e)
|
||||
{
|
||||
// When a content node contains nested content property
|
||||
// Check if the copied node contains a nested content
|
||||
var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent);
|
||||
UpdateNestedContentProperties(nestedContentProps, false);
|
||||
}
|
||||
|
||||
private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e)
|
||||
{
|
||||
// One or more content nodes could be saved in a bulk publish
|
||||
foreach (var entity in e.SavedEntities)
|
||||
{
|
||||
// When a content node contains nested content property
|
||||
// Check if the copied node contains a nested content
|
||||
var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent);
|
||||
UpdateNestedContentProperties(nestedContentProps, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
ContentService.Copying -= ContentService_Copying;
|
||||
ContentService.Saving -= ContentService_Saving;
|
||||
}
|
||||
|
||||
private void UpdateNestedContentProperties(IEnumerable<IProperty> nestedContentProps, bool onlyMissingKeys)
|
||||
{
|
||||
// Each NC Property on a doctype
|
||||
foreach (var nestedContentProp in nestedContentProps)
|
||||
{
|
||||
// A NC Prop may have one or more values due to cultures
|
||||
var propVals = nestedContentProp.Values;
|
||||
foreach (var cultureVal in propVals)
|
||||
{
|
||||
// Remove keys from published value & any nested NC's
|
||||
var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), onlyMissingKeys);
|
||||
cultureVal.PublishedValue = updatedPublishedVal;
|
||||
|
||||
// Remove keys from edited/draft value & any nested NC's
|
||||
var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), onlyMissingKeys);
|
||||
cultureVal.EditedValue = updatedEditedVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal for tests
|
||||
internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func<Guid> createGuid = null)
|
||||
{
|
||||
// used so we can test nicely
|
||||
if (createGuid == null)
|
||||
createGuid = () => Guid.NewGuid();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawJson) || !rawJson.DetectIsJson())
|
||||
return rawJson;
|
||||
|
||||
// Parse JSON
|
||||
var complexEditorValue = JToken.Parse(rawJson);
|
||||
|
||||
UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid);
|
||||
|
||||
return complexEditorValue.ToString();
|
||||
}
|
||||
|
||||
private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func<Guid> createGuid)
|
||||
{
|
||||
// check if this is NC
|
||||
var isNestedContent = json.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false).Any();
|
||||
|
||||
// select all values (flatten)
|
||||
var allProperties = json.SelectTokens("$..*").OfType<JValue>().Select(x => x.Parent as JProperty).WhereNotNull().ToList();
|
||||
foreach (var prop in allProperties)
|
||||
{
|
||||
if (prop.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey)
|
||||
{
|
||||
// get it's sibling 'key' property
|
||||
var ncKeyVal = prop.Parent["key"] as JValue;
|
||||
// TODO: This bool seems odd, if the key is null, shouldn't we fill it in regardless of onlyMissingKeys?
|
||||
if ((onlyMissingKeys && ncKeyVal == null) || (!onlyMissingKeys && ncKeyVal != null))
|
||||
{
|
||||
// create or replace
|
||||
prop.Parent["key"] = createGuid().ToString();
|
||||
}
|
||||
}
|
||||
else if (!isNestedContent || prop.Name != "key")
|
||||
{
|
||||
// this is an arbitrary property that could contain a nested complex editor
|
||||
var propVal = prop.Value?.ToString();
|
||||
// check if this might contain a nested NC
|
||||
if (!propVal.IsNullOrWhiteSpace() && propVal.DetectIsJson() && propVal.InvariantContains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey))
|
||||
{
|
||||
// recurse
|
||||
var parsed = JToken.Parse(propVal);
|
||||
UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid);
|
||||
// set the value to the updated one
|
||||
prop.Value = parsed.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
9
src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
Normal file
9
src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class NestedContentPropertyComposer : ComponentComposer<NestedContentPropertyComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -25,7 +25,10 @@ namespace Umbraco.Web.Mvc
|
||||
|
||||
//we have no choice but to instantiate the controller
|
||||
var instance = factory.CreateController(requestContext, controllerName);
|
||||
return instance?.GetType();
|
||||
var controllerType = instance?.GetType();
|
||||
factory.ReleaseController(instance);
|
||||
|
||||
return controllerType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,10 @@ namespace Umbraco.Web.Mvc
|
||||
|
||||
//we have no choice but to instantiate the controller
|
||||
var instance = factory.CreateController(requestContext, controllerName);
|
||||
return instance?.GetType();
|
||||
var controllerType = instance?.GetType();
|
||||
factory.ReleaseController(instance);
|
||||
|
||||
return controllerType;
|
||||
}
|
||||
|
||||
return GetControllerType(requestContext, controllerName);
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
<Compile Include="Compose\AuditEventsComposer.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
|
||||
<Compile Include="Compose\NestedContentPropertyComponent.cs" />
|
||||
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
|
||||
<Compile Include="Macros\MacroRenderer.cs" />
|
||||
<Compile Include="Macros\MemberUserKeyProvider.cs" />
|
||||
@@ -183,6 +184,7 @@
|
||||
<Compile Include="AspNet\AspNetHttpContextAccessor.cs" />
|
||||
<Compile Include="AspNet\AspNetIpResolver.cs" />
|
||||
<Compile Include="AspNet\AspNetPasswordHasher.cs" />
|
||||
<Compile Include="Compose\NestedContentPropertyComposer.cs" />
|
||||
<Compile Include="RoutableDocumentFilter.cs" />
|
||||
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
|
||||
<Compile Include="Security\BackOfficeSignInManager.cs" />
|
||||
|
||||
Reference in New Issue
Block a user