Merge branch 'v8/dev' into v8/feature/4940-save-media-reference-instead-of-media-url

This commit is contained in:
Rasmus John Pedersen
2019-05-01 08:55:55 +02:00
297 changed files with 8343 additions and 5897 deletions
-1
View File
@@ -54,6 +54,5 @@
<!-- symbols -->
<file src="$BuildTmp$\bin\Umbraco.Core.pdb" target="lib" />
<file src="$BuildTmp$\..\src\Umbraco.Core\**\*.cs" exclude="$BuildTmp$\..\src\**\TemporaryGeneratedFile*.cs" target="src\Umbraco.Core" />
</files>
</package>
-2
View File
@@ -59,8 +59,6 @@
<!-- symbols -->
<file src="$BuildTmp$\bin\Umbraco.Web.pdb" target="lib" />
<file src="$BuildTmp$\..\src\Umbraco.Web\**\*.cs" exclude="$BuildTmp$\..\src\**\TemporaryGeneratedFile*.cs" target="src\Umbraco.Web" />
<file src="$BuildTmp$\bin\Umbraco.Examine.pdb" target="lib" />
<file src="$BuildTmp$\..\src\Umbraco.Examine\**\*.cs" exclude="$BuildTmp$\..\src\**\TemporaryGeneratedFile*.cs" target="src\Umbraco.Examine" />
</files>
</package>
+2 -2
View File
@@ -387,13 +387,13 @@
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
-Version "$($this.Version.Semver.ToString())" `
-Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log"
-Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." }
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" `
-Properties BuildTmp="$($this.BuildTemp)" `
-Version "$($this.Version.Semver.ToString())" `
-Symbols -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log"
-Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log"
if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." }
&$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" `
+95 -38
View File
@@ -70,7 +70,23 @@ namespace Umbraco.Core.Composing
}
}
private IEnumerable<Type> PrepareComposerTypes()
internal IEnumerable<Type> PrepareComposerTypes()
{
var requirements = GetRequirements();
// only for debugging, this is verbose
//_logger.Debug<Composers>(GetComposersReport(requirements));
var sortedComposerTypes = SortComposers(requirements);
// bit verbose but should help for troubleshooting
//var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine;
_logger.Debug<Composers>("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes);
return sortedComposerTypes;
}
internal Dictionary<Type, List<Type>> GetRequirements(bool throwOnMissing = true)
{
// create a list, remove those that cannot be enabled due to runtime level
var composerTypeList = _composerTypes
@@ -89,25 +105,69 @@ namespace Umbraco.Core.Composing
// enable or disable composers
EnableDisableComposers(composerTypeList);
// sort the composers according to their dependencies
var requirements = new Dictionary<Type, List<Type>>();
foreach (var type in composerTypeList) requirements[type] = null;
foreach (var type in composerTypeList)
void GatherInterfaces<TAttribute>(Type type, Func<TAttribute, Type> getTypeInAttribute, HashSet<Type> iset, List<Type> set2)
where TAttribute : Attribute
{
GatherRequirementsFromRequireAttribute(type, composerTypeList, requirements);
GatherRequirementsFromRequiredByAttribute(type, composerTypeList, requirements);
foreach (var attribute in type.GetCustomAttributes<TAttribute>())
{
var typeInAttribute = getTypeInAttribute(attribute);
if (typeInAttribute != null && // if the attribute references a type ...
typeInAttribute.IsInterface && // ... which is an interface ...
typeof(IComposer).IsAssignableFrom(typeInAttribute) && // ... which implements IComposer ...
!iset.Contains(typeInAttribute)) // ... which is not already in the list
{
// add it to the new list
iset.Add(typeInAttribute);
set2.Add(typeInAttribute);
// add all its interfaces implementing IComposer
foreach (var i in typeInAttribute.GetInterfaces().Where(x => typeof(IComposer).IsAssignableFrom(x)))
{
iset.Add(i);
set2.Add(i);
}
}
}
}
// only for debugging, this is verbose
//_logger.Debug<Composers>(GetComposersReport(requirements));
// gather interfaces too
var interfaces = new HashSet<Type>(composerTypeList.SelectMany(x => x.GetInterfaces().Where(y => typeof(IComposer).IsAssignableFrom(y))));
composerTypeList.AddRange(interfaces);
var list1 = composerTypeList;
while (list1.Count > 0)
{
var list2 = new List<Type>();
foreach (var t in list1)
{
GatherInterfaces<ComposeAfterAttribute>(t, a => a.RequiredType, interfaces, list2);
GatherInterfaces<ComposeBeforeAttribute>(t, a => a.RequiringType, interfaces, list2);
}
composerTypeList.AddRange(list2);
list1 = list2;
}
// sort the composers according to their dependencies
var requirements = new Dictionary<Type, List<Type>>();
foreach (var type in composerTypeList)
requirements[type] = null;
foreach (var type in composerTypeList)
{
GatherRequirementsFromAfterAttribute(type, composerTypeList, requirements, throwOnMissing);
GatherRequirementsFromBeforeAttribute(type, composerTypeList, requirements);
}
return requirements;
}
internal IEnumerable<Type> SortComposers(Dictionary<Type, List<Type>> requirements)
{
// sort composers
var graph = new TopoGraph<Type, KeyValuePair<Type, List<Type>>>(kvp => kvp.Key, kvp => kvp.Value);
graph.AddItems(requirements);
List<Type> sortedComposerTypes;
try
{
sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).Where(x => !x.IsInterface).ToList();
}
catch (Exception e)
{
@@ -117,40 +177,37 @@ namespace Umbraco.Core.Composing
throw;
}
// bit verbose but should help for troubleshooting
//var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine;
_logger.Debug<Composers>("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes);
return sortedComposerTypes;
}
private static string GetComposersReport(Dictionary<Type, List<Type>> requirements)
internal static string GetComposersReport(Dictionary<Type, List<Type>> requirements)
{
var text = new StringBuilder();
text.AppendLine("Composers & Dependencies:");
text.AppendLine(" < compose before");
text.AppendLine(" > compose after");
text.AppendLine(" : implements");
text.AppendLine(" = depends");
text.AppendLine();
bool HasReq(IEnumerable<Type> types, Type type)
=> types.Any(x => type.IsAssignableFrom(x) && !x.IsInterface);
foreach (var kvp in requirements)
{
var type = kvp.Key;
text.AppendLine(type.FullName);
foreach (var attribute in type.GetCustomAttributes<ComposeAfterAttribute>())
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
: ""));
foreach (var attribute in type.GetCustomAttributes<ComposeBeforeAttribute>())
text.AppendLine(" -< " + attribute.RequiringType);
foreach (var i in type.GetInterfaces())
{
text.AppendLine(" : " + i.FullName);
foreach (var attribute in i.GetCustomAttributes<ComposeAfterAttribute>())
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
: ""));
foreach (var attribute in i.GetCustomAttributes<ComposeBeforeAttribute>())
text.AppendLine(" -< " + attribute.RequiringType);
var weak = !(attribute.RequiredType.IsInterface ? attribute.Weak == false : attribute.Weak != true);
text.AppendLine(" > " + attribute.RequiredType +
(weak ? " (weak" : " (strong") + (HasReq(requirements.Keys, attribute.RequiredType) ? ", found" : ", missing") + ")");
}
foreach (var attribute in type.GetCustomAttributes<ComposeBeforeAttribute>())
text.AppendLine(" < " + attribute.RequiringType);
foreach (var i in type.GetInterfaces())
text.AppendLine(" : " + i.FullName);
if (kvp.Value != null)
foreach (var t in kvp.Value)
text.AppendLine(" = " + t);
@@ -221,16 +278,16 @@ namespace Umbraco.Core.Composing
types.Remove(kvp.Key);
}
private static void GatherRequirementsFromRequireAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
private static void GatherRequirementsFromAfterAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements, bool throwOnMissing = true)
{
// get 'require' attributes
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
var requireAttributes = type
var afterAttributes = type
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<ComposeAfterAttribute>()) // those marking interfaces
.Concat(type.GetCustomAttributes<ComposeAfterAttribute>()); // those marking the composer
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
foreach (var attr in requireAttributes)
foreach (var attr in afterAttributes)
{
if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
@@ -238,13 +295,13 @@ namespace Umbraco.Core.Composing
// unless strong, and then require at least one enabled composer implementing that interface
if (attr.RequiredType.IsInterface)
{
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x) && !x.IsInterface).ToList();
if (implems.Count > 0)
{
if (requirements[type] == null) requirements[type] = new List<Type>();
requirements[type].AddRange(implems);
}
else if (attr.Weak == false) // if explicitly set to !weak, is strong, else is weak
else if (attr.Weak == false && throwOnMissing) // if explicitly set to !weak, is strong, else is weak
throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
}
// requiring a class = require that the composer is enabled
@@ -256,28 +313,28 @@ namespace Umbraco.Core.Composing
if (requirements[type] == null) requirements[type] = new List<Type>();
requirements[type].Add(attr.RequiredType);
}
else if (attr.Weak != true) // if not explicitly set to weak, is strong
else if (attr.Weak != true && throwOnMissing) // if not explicitly set to weak, is strong
throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
}
}
}
private static void GatherRequirementsFromRequiredByAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
{
// get 'required' attributes
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
var requiredAttributes = type
var beforeAttributes = type
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<ComposeBeforeAttribute>()) // those marking interfaces
.Concat(type.GetCustomAttributes<ComposeBeforeAttribute>()); // those marking the composer
foreach (var attr in requiredAttributes)
foreach (var attr in beforeAttributes)
{
if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
// required by an interface = by any enabled composer implementing this that interface
if (attr.RequiringType.IsInterface)
{
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x) && !x.IsInterface).ToList();
foreach (var implem in implems)
{
if (requirements[implem] == null) requirements[implem] = new List<Type>();
@@ -315,7 +315,7 @@ namespace Umbraco.Core.Configuration
var hash = hashString.GenerateHash();
var siteTemp = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", hash);
return _localTempPath = System.IO.Path.Combine(siteTemp, "umbraco.config");
return _localTempPath = siteTemp;
//case LocalTempStorage.Default:
//case LocalTempStorage.Unknown:
+13 -1
View File
@@ -10,6 +10,18 @@ namespace Umbraco.Core
///</summary>
public static class EnumerableExtensions
{
internal static bool HasDuplicates<T>(this IEnumerable<T> items, bool includeNull)
{
var hs = new HashSet<T>();
foreach (var item in items)
{
if ((item != null || includeNull) && !hs.Add(item))
return true;
}
return false;
}
/// <summary>
/// Wraps this object instance into an IEnumerable{T} consisting of a single item.
/// </summary>
@@ -100,7 +112,7 @@ namespace Umbraco.Core
}
}
/// <summary>
/// Returns true if all items in the other collection exist in this collection
/// </summary>
+16 -10
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -29,11 +30,16 @@ namespace Umbraco.Core.Mapping
/// </remarks>
public class UmbracoMapper
{
private readonly Dictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>> _ctors
= new Dictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>>();
// note
//
// the outer dictionary *can* be modified, see GetCtor and GetMap, hence have to be ConcurrentDictionary
// the inner dictionaries are never modified and therefore can be simple Dictionary
private readonly Dictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>> _maps
= new Dictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>>();
private readonly ConcurrentDictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>> _ctors
= new ConcurrentDictionary<Type, Dictionary<Type, Func<object, MapperContext, object>>>();
private readonly ConcurrentDictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>> _maps
= new ConcurrentDictionary<Type, Dictionary<Type, Action<object, object, MapperContext>>>();
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoMapper"/> class.
@@ -101,16 +107,12 @@ namespace Umbraco.Core.Mapping
private Dictionary<Type, Func<object, MapperContext, object>> DefineCtors(Type sourceType)
{
if (!_ctors.TryGetValue(sourceType, out var sourceCtor))
sourceCtor = _ctors[sourceType] = new Dictionary<Type, Func<object, MapperContext, object>>();
return sourceCtor;
return _ctors.GetOrAdd(sourceType, _ => new Dictionary<Type, Func<object, MapperContext, object>>());
}
private Dictionary<Type, Action<object, object, MapperContext>> DefineMaps(Type sourceType)
{
if (!_maps.TryGetValue(sourceType, out var sourceMap))
sourceMap = _maps[sourceType] = new Dictionary<Type, Action<object, object, MapperContext>>();
return sourceMap;
return _maps.GetOrAdd(sourceType, _ => new Dictionary<Type, Action<object, object, MapperContext>>());
}
#endregion
@@ -326,6 +328,8 @@ namespace Umbraco.Core.Mapping
if (_ctors.TryGetValue(sourceType, out var sourceCtor) && sourceCtor.TryGetValue(targetType, out var ctor))
return ctor;
// we *may* run this more than once but it does not matter
ctor = null;
foreach (var (stype, sctors) in _ctors)
{
@@ -347,6 +351,8 @@ namespace Umbraco.Core.Mapping
if (_maps.TryGetValue(sourceType, out var sourceMap) && sourceMap.TryGetValue(targetType, out var map))
return map;
// we *may* run this more than once but it does not matter
map = null;
foreach (var (stype, smap) in _maps)
{
@@ -83,17 +83,25 @@ namespace Umbraco.Core.Migrations.Expressions.Create
}
/// <inheritdoc />
public ICreateConstraintOnTableBuilder PrimaryKey()
public ICreateConstraintOnTableBuilder PrimaryKey() => PrimaryKey(true);
/// <inheritdoc />
public ICreateConstraintOnTableBuilder PrimaryKey(bool clustered)
{
var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey);
expression.Constraint.IsPrimaryKeyClustered = clustered;
return new CreateConstraintBuilder(expression);
}
/// <inheritdoc />
public ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName)
public ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName) => PrimaryKey(primaryKeyName, true);
/// <inheritdoc />
public ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName, bool clustered)
{
var expression = new CreateConstraintExpression(_context, ConstraintType.PrimaryKey);
expression.Constraint.ConstraintName = primaryKeyName;
expression.Constraint.IsPrimaryKeyClustered = clustered;
return new CreateConstraintBuilder(expression);
}
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Migrations.Expressions.Create.Expressions
var constraintType = (Constraint.IsPrimaryKeyConstraint) ? "PRIMARY KEY" : "UNIQUE";
if (Constraint.IsPrimaryKeyConstraint && SqlSyntax.SupportsClustered())
constraintType += " CLUSTERED";
constraintType += Constraint.IsPrimaryKeyClustered ? " CLUSTERED" : " NONCLUSTERED";
if (Constraint.IsNonUniqueConstraint)
constraintType = string.Empty;
@@ -68,6 +68,16 @@ namespace Umbraco.Core.Migrations.Expressions.Create
/// </summary>
ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName);
/// <summary>
/// Builds a Create Primary Key expression.
/// </summary>
ICreateConstraintOnTableBuilder PrimaryKey(bool clustered);
/// <summary>
/// Builds a Create Primary Key expression.
/// </summary>
ICreateConstraintOnTableBuilder PrimaryKey(string primaryKeyName, bool clustered);
/// <summary>
/// Builds a Create Unique Constraint expression.
/// </summary>
@@ -226,6 +226,8 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 35, UniqueId = 35.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordQuestion, Name = Constants.Conventions.Member.PasswordQuestionLabel, SortOrder = 7, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 36, UniqueId = 36.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = null, Alias = Constants.Conventions.Member.PasswordAnswer, Name = Constants.Conventions.Member.PasswordAnswerLabel, SortOrder = 8, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte)ContentVariation.Nothing });
}
+19 -3
View File
@@ -95,6 +95,21 @@ namespace Umbraco.Core.Models
protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//enable this to detect duplicate property aliases. We do want this, however making this change in a
//patch release might be a little dangerous
////detect if there are any duplicate aliases - this cannot be allowed
//if (e.Action == NotifyCollectionChangedAction.Add
// || e.Action == NotifyCollectionChangedAction.Replace)
//{
// var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias);
// if (allAliases.HasDuplicates(false))
// {
// var newAliases = string.Join(", ", e.NewItems.Cast<PropertyType>().Select(x => x.Alias));
// throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}");
// }
//}
OnPropertyChanged(nameof(PropertyTypes));
}
@@ -388,15 +403,16 @@ namespace Umbraco.Core.Models
var group = PropertyGroups[propertyGroupName];
if (group == null) return;
// re-assign the group's properties to no group
// first remove the group
PropertyGroups.RemoveItem(propertyGroupName);
// Then re-assign the group's properties to no group
foreach (var property in group.PropertyTypes)
{
property.PropertyGroupId = null;
_noGroupPropertyTypes.Add(property);
}
// actually remove the group
PropertyGroups.RemoveItem(propertyGroupName);
OnPropertyChanged(nameof(PropertyGroups));
}
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models
public class PropertyCollection : KeyedCollection<string, Property>, INotifyCollectionChanged, IDeepCloneable
{
private readonly object _addLocker = new object();
internal Action OnAdd;
internal Func<Property, bool> AdditionValidator { get; set; }
/// <summary>
@@ -49,10 +49,12 @@ namespace Umbraco.Core.Models
/// </summary>
internal void Reset(IEnumerable<Property> properties)
{
//collection events will be raised in each of these calls
Clear();
//collection events will be raised in each of these calls
foreach (var property in properties)
Add(property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
@@ -60,8 +62,9 @@ namespace Umbraco.Core.Models
/// </summary>
protected override void SetItem(int index, Property property)
{
var oldItem = index >= 0 ? this[index] : property;
base.SetItem(index, property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property, index));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem));
}
/// <summary>
@@ -120,10 +123,8 @@ namespace Umbraco.Core.Models
}
}
//collection events will be raised in InsertItem with Add
base.Add(property);
OnAdd?.Invoke();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property));
}
}
@@ -19,9 +19,6 @@ namespace Umbraco.Core.Models
{
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
// TODO: this doesn't seem to be used anywhere
internal Action OnAdd;
internal PropertyGroupCollection()
{ }
@@ -37,16 +34,19 @@ namespace Umbraco.Core.Models
/// <remarks></remarks>
internal void Reset(IEnumerable<PropertyGroup> groups)
{
//collection events will be raised in each of these calls
Clear();
//collection events will be raised in each of these calls
foreach (var group in groups)
Add(group);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void SetItem(int index, PropertyGroup item)
{
var oldItem = index >= 0 ? this[index] : item;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem));
}
protected override void RemoveItem(int index)
@@ -84,6 +84,7 @@ namespace Umbraco.Core.Models
if (keyExists)
throw new Exception($"Naming conflict: Changing the name of PropertyGroup '{item.Name}' would result in duplicates");
//collection events will be raised in SetItem
SetItem(IndexOfKey(item.Id), item);
return;
}
@@ -96,16 +97,14 @@ namespace Umbraco.Core.Models
var exists = Contains(key);
if (exists)
{
//collection events will be raised in SetItem
SetItem(IndexOfKey(key), item);
return;
}
}
}
//collection events will be raised in InsertItem
base.Add(item);
OnAdd?.Invoke();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
finally
{
@@ -19,9 +19,6 @@ namespace Umbraco.Core.Models
[IgnoreDataMember]
private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim();
// TODO: This doesn't seem to be used
[IgnoreDataMember]
internal Action OnAdd;
internal PropertyTypeCollection(bool supportsPublishing)
{
@@ -43,36 +40,44 @@ namespace Umbraco.Core.Models
/// <remarks></remarks>
internal void Reset(IEnumerable<PropertyType> properties)
{
//collection events will be raised in each of these calls
Clear();
//collection events will be raised in each of these calls
foreach (var property in properties)
Add(property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
Add(property);
}
protected override void SetItem(int index, PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
var oldItem = index >= 0 ? this[index] : item;
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem));
item.PropertyChanged += Item_PropertyChanged;
}
protected override void RemoveItem(int index)
{
var removed = this[index];
base.RemoveItem(index);
removed.PropertyChanged -= Item_PropertyChanged;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
protected override void InsertItem(int index, PropertyType item)
{
item.SupportsPublishing = SupportsPublishing;
base.InsertItem(index, item);
base.InsertItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
item.PropertyChanged += Item_PropertyChanged;
}
protected override void ClearItems()
{
base.ClearItems();
foreach (var item in this)
item.PropertyChanged -= Item_PropertyChanged;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
@@ -91,6 +96,7 @@ namespace Umbraco.Core.Models
var exists = Contains(key);
if (exists)
{
//collection events will be raised in SetItem
SetItem(IndexOfKey(key), item);
return;
}
@@ -103,10 +109,8 @@ namespace Umbraco.Core.Models
item.SortOrder = this.Max(x => x.SortOrder) + 1;
}
//collection events will be raised in InsertItem
base.Add(item);
OnAdd?.Invoke();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
finally
{
@@ -115,6 +119,17 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Occurs when a property changes on a PropertyType that exists in this collection
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var propType = (PropertyType)sender;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, propType, propType));
}
/// <summary>
/// Determines whether this collection contains a <see cref="Property"/> whose alias matches the specified PropertyType.
/// </summary>
@@ -18,5 +18,6 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
public string ConstraintName { get; set; }
public string TableName { get; set; }
public ICollection<string> Columns = new HashSet<string>();
public bool IsPrimaryKeyClustered { get; set; }
}
}
@@ -379,6 +379,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return "variantName";
}
// content type alias is invariant
if(ordering.OrderBy.InvariantEquals("contentTypeAlias"))
{
var joins = Sql()
.InnerJoin<ContentTypeDto>("ctype").On<ContentDto, ContentTypeDto>((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype");
// see notes in ApplyOrdering: the field MUST be selected + aliased
sql = Sql(InsertBefore(sql, "FROM", ", " + SqlSyntax.GetFieldName<ContentTypeDto>(x => x.Alias, "ctype") + " AS ordering "), sql.Arguments);
sql = InsertJoins(sql, joins);
return "ordering";
}
// previously, we'd accept anything and just sanitize it - not anymore
throw new NotSupportedException($"Ordering by {ordering.OrderBy} not supported.");
}
@@ -240,6 +240,25 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
propertyIx++;
}
contentType.NoGroupPropertyTypes = noGroupPropertyTypes;
// ensure builtin properties
if (contentType is MemberType memberType)
{
// ensure that the group exists (ok if it already exists)
memberType.AddPropertyGroup(Constants.Conventions.Member.StandardPropertiesGroupName);
// ensure that property types exist (ok if they already exist)
foreach (var (alias, propertyType) in builtinProperties)
{
var added = memberType.AddPropertyType(propertyType, Constants.Conventions.Member.StandardPropertiesGroupName);
if (added)
{
var access = new MemberTypePropertyProfileAccess(false, false, false);
memberType.MemberTypePropertyTypes[alias] = access;
}
}
}
}
}
@@ -264,7 +283,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (contentType is MemberType memberType)
{
var access = new MemberTypePropertyProfileAccess(dto.ViewOnProfile, dto.CanEdit, dto.IsSensitive);
memberType.MemberTypePropertyTypes.Add(dto.Alias, access);
memberType.MemberTypePropertyTypes[dto.Alias] = access;
}
return new PropertyType(dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias)
@@ -13,12 +13,15 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
public class ConfigurationEditor : IConfigurationEditor
{
private IDictionary<string, object> _defaultConfiguration;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationEditor"/> class.
/// </summary>
public ConfigurationEditor()
{
Fields = new List<ConfigurationField>();
_defaultConfiguration = new Dictionary<string, object>();
}
/// <summary>
@@ -61,7 +64,10 @@ namespace Umbraco.Core.PropertyEditors
/// <inheritdoc />
[JsonProperty("defaultConfig")]
public virtual IDictionary<string, object> DefaultConfiguration => new Dictionary<string, object>();
public virtual IDictionary<string, object> DefaultConfiguration {
get => _defaultConfiguration;
internal set => _defaultConfiguration = value;
}
/// <inheritdoc />
public virtual object DefaultConfigurationObject => DefaultConfiguration;
@@ -173,7 +173,13 @@ namespace Umbraco.Core.PropertyEditors
/// </summary>
protected virtual IConfigurationEditor CreateConfigurationEditor()
{
return new ConfigurationEditor();
var editor = new ConfigurationEditor();
// pass the default configuration if this is not a property value editor
if((Type & EditorType.PropertyValue) == 0)
{
editor.DefaultConfiguration = _defaultConfiguration;
}
return editor;
}
/// <summary>
@@ -1,8 +1,8 @@
namespace Umbraco.Core.PropertyEditors
{
internal class DropDownFlexibleConfiguration : ValueListConfiguration
public class DropDownFlexibleConfiguration : ValueListConfiguration
{
[ConfigurationField("multiple", "Enable multiple choice", "boolean", Description = "When checked, the dropdown will be a select multiple / combo box style dropdown.")]
public bool Multiple { get; set; }
}
}
}
@@ -9,18 +9,18 @@
public bool EnableRange { get; set; }
[ConfigurationField("initVal1", "Initial value", "number")]
public int InitialValue { get; set; }
public decimal InitialValue { get; set; }
[ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")]
public int InitialValue2 { get; set; }
public decimal InitialValue2 { get; set; }
[ConfigurationField("minVal", "Minimum value", "number")]
public int MinimumValue { get; set; }
public decimal MinimumValue { get; set; }
[ConfigurationField("maxVal", "Maximum value", "number")]
public int MaximumValue { get; set; }
public decimal MaximumValue { get; set; }
[ConfigurationField("step", "Step increments", "number")]
public int StepIncrements { get; set; }
public decimal StepIncrements { get; set; }
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -315,8 +316,16 @@ namespace Umbraco.Core.Services
/// <summary>
/// Empties the recycle bin.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
OperationResult EmptyRecycleBin();
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
/// <summary>
/// Sorts documents.
/// </summary>
@@ -162,8 +162,16 @@ namespace Umbraco.Core.Services
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
OperationResult EmptyRecycleBin();
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
/// <summary>
/// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
/// </summary>
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Umbraco.Core.Events;
@@ -1888,9 +1889,8 @@ namespace Umbraco.Core.Services.Implement
content.ParentId = parentId;
// get the level delta (old pos to new pos)
var levelDelta = parent == null
? 1 - content.Level + (parentId == Constants.System.RecycleBinContent ? 1 : 0)
: parent.Level + 1 - content.Level;
// note that recycle bin (id:-20) level is 0!
var levelDelta = 1 - content.Level + (parent?.Level ?? 0);
var paths = new Dictionary<int, string>();
@@ -1939,7 +1939,14 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
public OperationResult EmptyRecycleBin()
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
public OperationResult EmptyRecycleBin() => EmptyRecycleBin(Constants.Security.SuperUserId);
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId)
{
var nodeObjectType = Constants.ObjectTypes.Document;
var deleted = new List<IContent>();
@@ -1974,7 +1981,7 @@ namespace Umbraco.Core.Services.Implement
recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?!
scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs);
scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.Remove)).ToEventArgs());
Audit(AuditType.Delete, 0, Constants.System.RecycleBinContent, "Recycle bin emptied");
Audit(AuditType.Delete, userId, Constants.System.RecycleBinContent, "Recycle bin emptied");
scope.Complete();
}
@@ -2878,25 +2885,16 @@ namespace Umbraco.Core.Services.Implement
{
foreach (var property in blueprint.Properties)
{
if (property.PropertyType.VariesByCulture())
{
content.SetValue(property.Alias, property.GetValue(culture), culture);
}
else
{
content.SetValue(property.Alias, property.GetValue());
}
var propertyCulture = property.PropertyType.VariesByCulture() ? culture : null;
content.SetValue(property.Alias, property.GetValue(propertyCulture), propertyCulture);
}
content.Name = blueprint.Name;
if (!string.IsNullOrEmpty(culture))
{
content.SetCultureInfo(culture, blueprint.GetCultureName(culture), now);
}
}
return content;
}
@@ -1034,10 +1034,12 @@ namespace Umbraco.Core.Services.Implement
//strip the @inherits if it's there
snippetContent = StripPartialViewHeader(snippetContent);
//Update Model.Content. to be Model. when used as PartialView
//Update Model.Content to be Model when used as PartialView
if (partialViewType == PartialViewType.PartialView)
{
snippetContent = snippetContent.Replace("Model.Content.", "Model.");
snippetContent = snippetContent
.Replace("Model.Content.", "Model.")
.Replace("(Model.Content)", "(Model)");
}
var content = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -289,7 +290,7 @@ namespace Umbraco.Core.Services.Implement
scope.Events.Dispatch(Saved, this, saveEventArgs);
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IMedia>(media, TreeChangeTypes.RefreshNode).ToEventArgs());
}
if (withIdentity == false)
return;
@@ -716,7 +717,7 @@ namespace Umbraco.Core.Services.Implement
#endregion
#region Delete
/// <summary>
/// Permanently deletes an <see cref="IMedia"/> object
/// </summary>
@@ -975,9 +976,8 @@ namespace Umbraco.Core.Services.Implement
media.ParentId = parentId;
// get the level delta (old pos to new pos)
var levelDelta = parent == null
? 1 - media.Level + (parentId == Constants.System.RecycleBinMedia ? 1 : 0)
: parent.Level + 1 - media.Level;
// note that recycle bin (id:-20) level is 0!
var levelDelta = 1 - media.Level + (parent?.Level ?? 0);
var paths = new Dictionary<int, string>();
@@ -1024,7 +1024,15 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
public OperationResult EmptyRecycleBin()
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
public OperationResult EmptyRecycleBin() => EmptyRecycleBin(Constants.Security.SuperUserId);
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
/// <param name="userId">Optional Id of the User emptying the Recycle Bin</param>
public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId)
{
var nodeObjectType = Constants.ObjectTypes.Media;
var deleted = new List<IMedia>();
@@ -1063,7 +1071,7 @@ namespace Umbraco.Core.Services.Implement
args.CanCancel = false;
scope.Events.Dispatch(EmptiedRecycleBin, this, args);
scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange<IMedia>(x, TreeChangeTypes.Remove)).ToEventArgs());
Audit(AuditType.Delete, 0, Constants.System.RecycleBinMedia, "Empty Media recycle bin");
Audit(AuditType.Delete, userId, Constants.System.RecycleBinMedia, "Empty Media recycle bin");
scope.Complete();
}
@@ -159,6 +159,7 @@ namespace Umbraco.Tests.Cache
TestObjects.GetUmbracoSettings(),
TestObjects.GetGlobalSettings(),
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
// just assert it does not throw
@@ -81,6 +81,7 @@ namespace Umbraco.Tests.Cache.PublishedCache
new WebSecurity(_httpContextFactory.HttpContext, Current.Services.UserService, globalSettings),
umbracoSettings,
Enumerable.Empty<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
globalSettings,
new TestVariationContextAccessor());
@@ -4,6 +4,7 @@ using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Compose;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
@@ -299,11 +300,19 @@ namespace Umbraco.Tests.Components
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
Composed.Clear();
Assert.Throws<Exception>(() => composers.Compose());
Console.WriteLine("throws:");
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements(false);
Console.WriteLine(Composers.GetComposersReport(requirements));
types = new[] { typeof(Composer2) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
Composed.Clear();
Assert.Throws<Exception>(() => composers.Compose());
Console.WriteLine("throws:");
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
requirements = composers.GetRequirements(false);
Console.WriteLine(Composers.GetComposersReport(requirements));
types = new[] { typeof(Composer12) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
@@ -349,6 +358,25 @@ namespace Umbraco.Tests.Components
Assert.AreEqual(typeof(Composer27), Composed[1]);
}
[Test]
public void AllComposers()
{
var typeLoader = new TypeLoader(AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of<IProfilingLogger>());
var register = MockRegister();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var types = typeLoader.GetTypes<IComposer>().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web"));
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements();
var report = Composers.GetComposersReport(requirements);
Console.WriteLine(report);
var composerTypes = composers.SortComposers(requirements);
foreach (var type in composerTypes)
Console.WriteLine(type);
}
#region Compothings
public class TestComposerBase : IComposer
@@ -27,10 +27,7 @@ namespace Umbraco.Tests.Composing
public void Initialize()
{
// this ensures it's reset
_typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
foreach (var file in Directory.GetFiles(IOHelper.MapPath(SystemDirectories.TempData.EnsureEndsWith('/') + "TypesCache")))
File.Delete(file);
_typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()), false);
// for testing, we'll specify which assemblies are scanned for the PluginTypeResolver
// TODO: Should probably update this so it only searches this assembly and add custom types to be found
@@ -381,6 +381,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
}
}
public override IPublishedContent GetById(bool preview, Udi nodeId)
=> throw new NotSupportedException();
public override bool HasById(bool preview, int contentId)
{
return GetXml(preview).CreateNavigator().MoveToId(contentId.ToString(CultureInfo.InvariantCulture));
@@ -97,6 +97,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
throw new NotImplementedException();
}
public override IPublishedContent GetById(bool preview, Udi nodeId)
=> throw new NotSupportedException();
public override bool HasById(bool preview, int contentId)
{
return GetUmbracoMedia(contentId) != null;
+81 -1
View File
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
@@ -108,6 +110,68 @@ namespace Umbraco.Tests.Mapping
var target = mapper.Map<IEnumerable<ContentPropertyDto>>(source);
}
[Test]
[Explicit]
public void ConcurrentMap()
{
var definitions = new MapDefinitionCollection(new IMapDefinition[]
{
new MapperDefinition1(),
new MapperDefinition3(),
});
var mapper = new UmbracoMapper(definitions);
// the mapper currently has a map from Thing1 to Thing2
// because Thing3 inherits from Thing1, it will map a Thing3 instance,
// and register a new map from Thing3 to Thing2,
// thus modifying its internal dictionaries
// if timing is good, and mapper does have non-concurrent dictionaries, it fails
// practically, to reproduce, one needs to add a 1s sleep in the mapper's loop
// hence, this test is explicit
var thing3 = new Thing3 { Value = "value" };
var thing4 = new Thing4();
Exception caught = null;
void ThreadLoop()
{
// keep failing at mapping - and looping through the maps
for (var i = 0; i < 10; i++)
{
try
{
mapper.Map<Thing2>(thing4);
}
catch (Exception e)
{
caught = e;
Console.WriteLine($"{e.GetType().Name} {e.Message}");
}
}
Console.WriteLine("done");
}
var thread = new Thread(ThreadLoop);
thread.Start();
Thread.Sleep(1000);
try
{
Console.WriteLine($"{DateTime.Now:O} mapping");
var thing2 = mapper.Map<Thing2>(thing3);
Console.WriteLine($"{DateTime.Now:O} mapped");
Assert.IsNotNull(thing2);
Assert.AreEqual("value", thing2.Value);
}
finally
{
thread.Join();
}
}
private class Thing1
{
public string Value { get; set; }
@@ -121,6 +185,9 @@ namespace Umbraco.Tests.Mapping
public string Value { get; set; }
}
private class Thing4
{ }
private class MapperDefinition1 : IMapDefinition
{
public void DefineMaps(UmbracoMapper mapper)
@@ -144,5 +211,18 @@ namespace Umbraco.Tests.Mapping
private static void Map(Property source, ContentPropertyDto target, MapperContext context)
{ }
}
private class MapperDefinition3 : IMapDefinition
{
public void DefineMaps(UmbracoMapper mapper)
{
// just some random things so that the mapper contains things
mapper.Define<int, object>();
mapper.Define<string, object>();
mapper.Define<double, object>();
mapper.Define<UmbracoMapper, object>();
mapper.Define<Property, object>();
}
}
}
}
+43 -9
View File
@@ -15,13 +15,47 @@ namespace Umbraco.Tests.Models
[TestFixture]
public class ContentTypeTests : UmbracoTestBase
{
[Test]
[Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")]
public void Cannot_Add_Duplicate_Property_Aliases()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.PropertyGroups.Add(new PropertyGroup(new PropertyTypeCollection(false, new[]
{
new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar){ Alias = "myPropertyType" }
})));
Assert.Throws<InvalidOperationException>(() =>
contentType.PropertyTypeCollection.Add(
new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar) { Alias = "myPropertyType" }));
}
[Test]
[Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")]
public void Cannot_Update_Duplicate_Property_Aliases()
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.PropertyGroups.Add(new PropertyGroup(new PropertyTypeCollection(false, new[]
{
new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar){ Alias = "myPropertyType" }
})));
contentType.PropertyTypeCollection.Add(new PropertyType("testPropertyEditor", ValueStorageType.Nvarchar) { Alias = "myPropertyType2" });
var toUpdate = contentType.PropertyTypeCollection["myPropertyType2"];
Assert.Throws<InvalidOperationException>(() => toUpdate.Alias = "myPropertyType");
}
[Test]
public void Can_Deep_Clone_Content_Type_Sort()
{
var contentType = new ContentTypeSort(new Lazy<int>(() => 3), 4, "test");
var clone = (ContentTypeSort) contentType.DeepClone();
var clone = (ContentTypeSort)contentType.DeepClone();
Assert.AreNotSame(clone, contentType);
Assert.AreEqual(clone, contentType);
Assert.AreEqual(clone.Id.Value, contentType.Id.Value);
@@ -54,7 +88,7 @@ namespace Umbraco.Tests.Models
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -117,12 +151,12 @@ namespace Umbraco.Tests.Models
{
group.Id = ++i;
}
contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } };
contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2") };
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -167,12 +201,12 @@ namespace Umbraco.Tests.Models
{
group.Id = ++i;
}
contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] {new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2")};
contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2") };
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -264,12 +298,12 @@ namespace Umbraco.Tests.Models
{
propertyType.Id = ++i;
}
contentType.AllowedTemplates = new[] { new Template((string) "Name", (string) "name") { Id = 200 }, new Template((string) "Name2", (string) "name2") { Id = 201 } };
contentType.AllowedTemplates = new[] { new Template((string)"Name", (string)"name") { Id = 200 }, new Template((string)"Name2", (string)"name2") { Id = 201 } };
contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy<int>(() => 888), 8, "sub"), new ContentTypeSort(new Lazy<int>(() => 889), 9, "sub2") };
contentType.Id = 10;
contentType.CreateDate = DateTime.Now;
contentType.CreatorId = 22;
contentType.SetDefaultTemplate(new Template((string) "Test Template", (string) "testTemplate")
contentType.SetDefaultTemplate(new Template((string)"Test Template", (string)"testTemplate")
{
Id = 88
});
@@ -171,7 +171,6 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
//NOTE: This tests for left join logic (rev 7b14e8eacc65f82d4f184ef46c23340c09569052)
[Test]
public void Can_Get_All_Members_When_No_Properties_Assigned()
@@ -200,7 +199,6 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Get_Member_Type_By_Id()
{
@@ -233,22 +231,114 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
// See: https://github.com/umbraco/Umbraco-CMS/issues/4963#issuecomment-483516698
[Test]
public void Bug_Changing_Built_In_Member_Type_Property_Type_Aliases_Results_In_Exception()
{
var stubs = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
var provider = TestObjects.GetScopeProvider(Logger);
using (provider.CreateScope())
{
var repository = CreateRepository(provider);
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType("mtype");
// created without the stub properties
Assert.AreEqual(1, memberType.PropertyGroups.Count);
Assert.AreEqual(3, memberType.PropertyTypes.Count());
// saving *new* member type adds the stub properties
repository.Save(memberType);
// saving has added (and saved) the stub properties
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count());
foreach (var stub in stubs)
{
var prop = memberType.PropertyTypes.First(x => x.Alias == stub.Key);
prop.Alias = prop.Alias + "__0000";
}
// saving *existing* member type does *not* ensure stub properties
repository.Save(memberType);
// therefore, nothing has changed
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count());
// fetching ensures that the stub properties are there
memberType = repository.Get("mtype");
Assert.IsNotNull(memberType);
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count * 2, memberType.PropertyTypes.Count());
}
}
[Test]
public void Built_In_Member_Type_Properties_Are_Automatically_Added_When_Creating()
{
var stubs = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
using (provider.CreateScope())
{
var repository = CreateRepository(provider);
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
// created without the stub properties
Assert.AreEqual(1, memberType.PropertyGroups.Count);
Assert.AreEqual(3, memberType.PropertyTypes.Count());
// saving *new* member type adds the stub properties
repository.Save(memberType);
// saving has added (and saved) the stub properties
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count());
// getting with stub properties
memberType = repository.Get(memberType.Id);
Assert.That(memberType.PropertyTypes.Count(), Is.EqualTo(3 + Constants.Conventions.Member.GetStandardPropertyTypeStubs().Count));
Assert.That(memberType.PropertyGroups.Count(), Is.EqualTo(2));
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count());
}
}
[Test]
public void Built_In_Member_Type_Properties_Missing_Are_Automatically_Added_When_Creating()
{
var stubs = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
var provider = TestObjects.GetScopeProvider(Logger);
using (provider.CreateScope())
{
var repository = CreateRepository(provider);
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
// created without the stub properties
Assert.AreEqual(1, memberType.PropertyGroups.Count);
Assert.AreEqual(3, memberType.PropertyTypes.Count());
// add one stub property, others are still missing
memberType.AddPropertyType(stubs.First().Value, Constants.Conventions.Member.StandardPropertiesGroupName);
// saving *new* member type adds the (missing) stub properties
repository.Save(memberType);
// saving has added (and saved) the (missing) stub properties
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count());
// getting with stub properties
memberType = repository.Get(memberType.Id);
Assert.AreEqual(2, memberType.PropertyGroups.Count);
Assert.AreEqual(3 + stubs.Count, memberType.PropertyTypes.Count());
}
}
@@ -71,6 +71,7 @@ namespace Umbraco.Tests.PublishedContent
new WebSecurity(httpContext, Current.Services.UserService, globalSettings),
TestObjects.GetUmbracoSettings(),
Enumerable.Empty<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
globalSettings,
new TestVariationContextAccessor());
@@ -92,6 +92,9 @@ namespace Umbraco.Tests.PublishedContent
throw new NotImplementedException();
}
public override IPublishedContent GetById(bool preview, Udi nodeId)
=> throw new NotSupportedException();
public override bool HasById(bool preview, int contentId)
{
return _content.ContainsKey(contentId);
@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Tests.PublishedContent;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Stubs;
using Umbraco.Tests.Testing;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.Routing
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
public class MediaUrlProviderTests : BaseWebTest
{
private DefaultMediaUrlProvider _mediaUrlProvider;
public override void SetUp()
{
base.SetUp();
_mediaUrlProvider = new DefaultMediaUrlProvider();
}
public override void TearDown()
{
base.TearDown();
_mediaUrlProvider = null;
}
[Test]
public void Get_Media_Url_Resolves_Url_From_Upload_Property_Editor()
{
const string expected = "/media/rfeiw584/test.jpg";
var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider });
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null);
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, null, null);
Assert.AreEqual(expected, resolvedUrl);
}
[Test]
public void Get_Media_Url_Resolves_Url_From_Image_Cropper_Property_Editor()
{
const string expected = "/media/rfeiw584/test.jpg";
var configuration = new ImageCropperConfiguration();
var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue
{
Src = expected
});
var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider });
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration);
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, null, null);
Assert.AreEqual(expected, resolvedUrl);
}
[Test]
public void Get_Media_Url_Can_Resolve_Absolute_Url()
{
const string mediaUrl = "/media/rfeiw584/test.jpg";
var expected = $"http://localhost{mediaUrl}";
var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider });
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, mediaUrl, null);
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Absolute, null, null);
Assert.AreEqual(expected, resolvedUrl);
}
[Test]
public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported()
{
var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider });
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.Boolean, "0", null);
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "test", UrlProviderMode.Absolute, null, null);
Assert.AreEqual(string.Empty, resolvedUrl);
}
[Test]
public void Get_Media_Url_Can_Resolve_Variant_Property_Url()
{
var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider });
var umbracoFilePropertyType = CreatePropertyType(Constants.PropertyEditors.Aliases.UploadField, null, ContentVariation.Culture);
const string enMediaUrl = "/media/rfeiw584/en.jpg";
const string daMediaUrl = "/media/uf8ewud2/da.jpg";
var property = new SolidPublishedPropertyWithLanguageVariants
{
Alias = "umbracoFile",
PropertyType = umbracoFilePropertyType,
};
property.SetValue("en", enMediaUrl, true);
property.SetValue("da", daMediaUrl);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), new [] { umbracoFilePropertyType }, ContentVariation.Culture);
var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}};
var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, "umbracoFile", UrlProviderMode.Auto, "da", null);
Assert.AreEqual(daMediaUrl, resolvedUrl);
}
private static TestPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration)
{
var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(),
new[] {umbracoFilePropertyType}, ContentVariation.Nothing);
return new TestPublishedContent(contentType, 1234, Guid.NewGuid(),
new Dictionary<string, object> {{"umbracoFile", propertyValue } }, false);
}
private static PublishedPropertyType CreatePropertyType(string propertyEditorAlias, object dataTypeConfiguration, ContentVariation variation)
{
var uploadDataType = new PublishedDataType(1234, propertyEditorAlias, new Lazy<object>(() => dataTypeConfiguration));
var propertyValueConverters = new PropertyValueConverterCollection(new IPropertyValueConverter[]
{
new UploadPropertyConverter(),
new ImageCropperValueConverter(),
});
var publishedModelFactory = Mock.Of<IPublishedModelFactory>();
var publishedContentTypeFactory = new Mock<IPublishedContentTypeFactory>();
publishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny<int>()))
.Returns(uploadDataType);
return new PublishedPropertyType("umbracoFile", 42, true, variation, propertyValueConverters, publishedModelFactory, publishedContentTypeFactory.Object);
}
}
}
@@ -118,6 +118,7 @@ namespace Umbraco.Tests.Scoping
new WebSecurity(httpContext, Current.Services.UserService, globalSettings),
umbracoSettings ?? SettingsForTests.GetDefaultUmbracoSettings(),
urlProviders ?? Enumerable.Empty<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
globalSettings,
new TestVariationContextAccessor());
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;
using Microsoft.Owin;
using Moq;
@@ -33,7 +34,7 @@ namespace Umbraco.Tests.Security
Mock.Of<HttpContextBase>(),
Mock.Of<IPublishedSnapshotService>(),
new WebSecurity(Mock.Of<HttpContextBase>(), Current.Services.UserService, globalSettings),
TestObjects.GetUmbracoSettings(), new List<IUrlProvider>(),globalSettings,
TestObjects.GetUmbracoSettings(), new List<IUrlProvider>(), Enumerable.Empty<IMediaUrlProvider>(), globalSettings,
new TestVariationContextAccessor());
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Install);
@@ -53,7 +54,7 @@ namespace Umbraco.Tests.Security
Mock.Of<HttpContextBase>(),
Mock.Of<IPublishedSnapshotService>(),
new WebSecurity(Mock.Of<HttpContextBase>(), Current.Services.UserService, globalSettings),
TestObjects.GetUmbracoSettings(), new List<IUrlProvider>(), globalSettings,
TestObjects.GetUmbracoSettings(), new List<IUrlProvider>(), Enumerable.Empty<IMediaUrlProvider>(), globalSettings,
new TestVariationContextAccessor());
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
@@ -139,6 +139,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
webSecurity.Object,
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
Enumerable.Empty<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
globalSettings,
new TestVariationContextAccessor());
@@ -122,6 +122,7 @@ namespace Umbraco.Tests.TestHelpers
var umbracoSettings = GetUmbracoSettings();
var globalSettings = GetGlobalSettings();
var urlProviders = new UrlProviderCollection(Enumerable.Empty<IUrlProvider>());
var mediaUrlProviders = new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>());
if (accessor == null) accessor = new TestUmbracoContextAccessor();
@@ -133,6 +134,7 @@ namespace Umbraco.Tests.TestHelpers
umbracoSettings,
globalSettings,
urlProviders,
mediaUrlProviders,
Mock.Of<IUserService>());
return umbracoContextFactory.EnsureUmbracoContext(httpContext).UmbracoContext;
@@ -353,7 +353,7 @@ namespace Umbraco.Tests.TestHelpers
}
}
protected UmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable<IUrlProvider> urlProviders = null, IGlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null)
protected UmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable<IUrlProvider> urlProviders = null, IEnumerable<IMediaUrlProvider> mediaUrlProviders = null, IGlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null)
{
// ensure we have a PublishedCachesService
var service = snapshotService ?? PublishedSnapshotService as PublishedSnapshotService;
@@ -380,6 +380,7 @@ namespace Umbraco.Tests.TestHelpers
Factory.GetInstance<IGlobalSettings>()),
umbracoSettings ?? Factory.GetInstance<IUmbracoSettingsSection>(),
urlProviders ?? Enumerable.Empty<IUrlProvider>(),
mediaUrlProviders ?? Enumerable.Empty<IMediaUrlProvider>(),
globalSettings ?? Factory.GetInstance<IGlobalSettings>(),
new TestVariationContextAccessor());
@@ -80,7 +80,7 @@ namespace Umbraco.Tests.Testing.TestingTests
.Returns(UrlInfo.Url("/hello/world/1234"));
var urlProvider = urlProviderMock.Object;
var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, umbracoContext.VariationContextAccessor);
var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, Enumerable.Empty<IMediaUrlProvider>(), umbracoContext.VariationContextAccessor);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = Mock.Of<IPublishedContent>();
+1
View File
@@ -143,6 +143,7 @@
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
<Compile Include="PublishedContent\NuCacheTests.cs" />
<Compile Include="Routing\MediaUrlProviderTests.cs" />
<Compile Include="Runtimes\StandaloneTests.cs" />
<Compile Include="Routing\GetContentUrlsTests.cs" />
<Compile Include="Services\AmbiguousEventTests.cs" />
@@ -72,6 +72,7 @@ namespace Umbraco.Tests.Web.Mvc
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -101,6 +102,7 @@ namespace Umbraco.Tests.Web.Mvc
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -130,6 +132,7 @@ namespace Umbraco.Tests.Web.Mvc
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -159,6 +162,7 @@ namespace Umbraco.Tests.Web.Mvc
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -47,6 +47,7 @@ namespace Umbraco.Tests.Web.Mvc
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -74,6 +75,7 @@ namespace Umbraco.Tests.Web.Mvc
TestObjects.GetUmbracoSettings(),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -104,6 +106,7 @@ namespace Umbraco.Tests.Web.Mvc
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -141,6 +144,7 @@ namespace Umbraco.Tests.Web.Mvc
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == webRoutingSettings),
globalSettings,
new UrlProviderCollection(Enumerable.Empty<IUrlProvider>()),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>());
@@ -440,6 +440,7 @@ namespace Umbraco.Tests.Web.Mvc
new WebSecurity(http, Current.Services.UserService, globalSettings),
TestObjects.GetUmbracoSettings(),
Enumerable.Empty<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
globalSettings,
new TestVariationContextAccessor());
@@ -111,6 +111,7 @@ namespace Umbraco.Tests.Web
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(new[] { testUrlProvider.Object }),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
@@ -31,6 +32,7 @@ namespace Umbraco.Tests.Web
new WebSecurity(Mock.Of<HttpContextBase>(), Current.Services.UserService, TestObjects.GetGlobalSettings()),
TestObjects.GetUmbracoSettings(),
new List<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
TestObjects.GetGlobalSettings(),
new TestVariationContextAccessor());
var r1 = new RouteData();
@@ -49,6 +51,7 @@ namespace Umbraco.Tests.Web
new WebSecurity(Mock.Of<HttpContextBase>(), Current.Services.UserService, TestObjects.GetGlobalSettings()),
TestObjects.GetUmbracoSettings(),
new List<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
TestObjects.GetGlobalSettings(),
new TestVariationContextAccessor());
@@ -77,6 +80,7 @@ namespace Umbraco.Tests.Web
new WebSecurity(Mock.Of<HttpContextBase>(), Current.Services.UserService, TestObjects.GetGlobalSettings()),
TestObjects.GetUmbracoSettings(),
new List<IUrlProvider>(),
Enumerable.Empty<IMediaUrlProvider>(),
TestObjects.GetGlobalSettings(),
new TestVariationContextAccessor());
+9 -2
View File
@@ -1,3 +1,10 @@
{
"presets": ["@babel/preset-env"]
}
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 version, not dead, > 0.5%, not ie 11"
}
]
]
}
@@ -7,6 +7,7 @@ var babel = require("gulp-babel");
var sort = require('gulp-sort');
var concat = require('gulp-concat');
var wrap = require("gulp-wrap-js");
var embedTemplates = require('gulp-angular-embed-templates');
module.exports = function(files, out) {
@@ -22,6 +23,7 @@ module.exports = function(files, out) {
// sort files in stream by path or any custom sort comparator
task = task.pipe(babel())
.pipe(sort())
.pipe(embedTemplates({ basePath: "./src/" }))
.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(config.root + config.targets.js));
@@ -29,4 +31,4 @@ module.exports = function(files, out) {
return task;
};
};
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -29,7 +29,7 @@
"diff": "3.5.0",
"flatpickr": "4.5.2",
"font-awesome": "4.7.0",
"jquery": "3.3.1",
"jquery": "^3.4.0",
"jquery-ui-dist": "1.12.1",
"jquery-ui-touch-punch": "0.2.3",
"lazyload-js": "1.0.0",
@@ -50,6 +50,7 @@
"cssnano": "4.1.7",
"fs": "0.0.2",
"gulp": "^3.9.1",
"gulp-angular-embed-templates": "^2.3.0",
"gulp-babel": "8.0.0",
"gulp-clean-css": "4.0.0",
"gulp-cli": "^2.0.1",
@@ -126,6 +126,7 @@
function togglePassword() {
var elem = $("form[name='vm.loginForm'] input[name='password']");
elem.attr("type", (elem.attr("type") === "text" ? "password" : "text"));
elem.focus();
$(".password-text.show, .password-text.hide").toggle();
}
@@ -117,6 +117,8 @@ Use this directive to render an umbraco button. The directive can be used to gen
vm.innerState = "init";
vm.buttonLabel = vm.label;
// is this a primary button style (i.e. anything but an 'info' button)?
vm.isPrimaryButtonStyle = vm.buttonStyle && vm.buttonStyle !== 'info';
if (vm.buttonStyle) {
@@ -0,0 +1,105 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbToggleGroup
@restrict E
@scope
@description
Use this directive to render a group of toggle buttons.
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-toggle-group
items="vm.items"
on-click="vm.toggle(item)">
</umb-toggle-group>
</div>
</pre>
<h3>Controller example</h3>
<pre>
(function () {
"use strict";
function Controller() {
var vm = this;
vm.toggle = toggle;
function toggle(item) {
if(item.checked) {
// do something if item is checked
}
else {
// do something else if item is unchecked
}
}
function init() {
vm.items = [{
name: "Item 1",
description: "Item 1 description",
checked: false,
disabled: false
}, {
name: "Item 2",
description: "Item 2 description",
checked: true,
disabled: true
}];
}
init();
}
angular.module("umbraco").controller("My.Controller", Controller);
})();
</pre>
@param {Array} items The items to list in the toggle group
@param {callback} onClick The function which should be called when the toggle is clicked for one of the items.
**/
(function () {
'use strict';
function ToggleGroupDirective() {
function link(scope, el, attr, ctrl) {
scope.change = function(item) {
if (item.disabled) {
return;
}
item.checked = !item.checked;
if(scope.onClick) {
scope.onClick({'item': item});
}
};
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/buttons/umb-toggle-group.html',
scope: {
items: "=",
onClick: "&"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbToggleGroup', ToggleGroupDirective);
})();
@@ -55,9 +55,9 @@
}
}
// if we still dont have a app, lets show the first one:
if (isAppPresent === false) {
if (isAppPresent === false && content.apps.length) {
content.apps[0].active = true;
$scope.appChanged(content.apps[0]);
}
@@ -198,9 +198,11 @@
return a.alias === "umbContent";
});
//The view model for the content app is simply the index of the variant being edited
var variantIndex = vm.content.variants.indexOf(variant);
contentApp.viewModel = variantIndex;
if (contentApp) {
//The view model for the content app is simply the index of the variant being edited
var variantIndex = vm.content.variants.indexOf(variant);
contentApp.viewModel = variantIndex;
}
// make sure the same app it set to active in the new variant
if(activeAppAlias) {
@@ -47,6 +47,9 @@ The sub header is sticky and will follow along down the page when scrolling.
transclude: true,
restrict: 'E',
replace: true,
scope: {
"appearance": "@?"
},
templateUrl: 'views/components/editor/subheader/umb-editor-sub-header.html'
};
@@ -6,7 +6,7 @@
**/
angular.module("umbraco.directives")
.directive('umbImageCrop',
function ($timeout, localizationService, cropperHelper, $log) {
function ($timeout, cropperHelper) {
return {
restrict: 'E',
replace: true,
@@ -21,6 +21,9 @@ angular.module("umbraco.directives")
},
link: function(scope, element, attrs) {
let sliderRef = null;
scope.width = 400;
scope.height = 320;
@@ -30,12 +33,56 @@ angular.module("umbraco.directives")
viewport:{},
margin: 20,
scale: {
min: 0.3,
min: 0,
max: 3,
current: 1
}
};
};
scope.sliderOptions = {
"start": scope.dimensions.scale.current,
"step": 0.001,
"tooltips": [false],
"format": {
to: function (value) {
return parseFloat(parseFloat(value).toFixed(3)); //Math.round(value);
},
from: function (value) {
return parseFloat(parseFloat(value).toFixed(3)); //Math.round(value);
}
},
"range": {
"min": scope.dimensions.scale.min,
"max": scope.dimensions.scale.max
}
};
scope.setup = function (slider) {
sliderRef = slider;
// Set slider handle position
sliderRef.noUiSlider.set(scope.dimensions.scale.current);
// Update slider range min/max
sliderRef.noUiSlider.updateOptions({
"range": {
"min": scope.dimensions.scale.min,
"max": scope.dimensions.scale.max
}
});
};
scope.slide = function (values) {
if (values) {
scope.dimensions.scale.current = parseFloat(values);
}
};
scope.change = function (values) {
if (values) {
scope.dimensions.scale.current = parseFloat(values);
}
};
//live rendering of viewport and image styles
scope.style = function () {
@@ -63,7 +110,6 @@ angular.module("umbraco.directives")
constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height;
};
var setDimensions = function(originalImage){
originalImage.width("auto");
originalImage.height("auto");
@@ -131,13 +177,11 @@ angular.module("umbraco.directives")
scope.dimensions.scale.current = scope.dimensions.image.ratio;
//min max based on original width/height
// Update min and max based on original width/height
scope.dimensions.scale.min = ratioCalculation.ratio;
scope.dimensions.scale.max = 2;
scope.dimensions.scale.max = 2;
};
var validatePosition = function(left, top){
if(left > constraints.left.max)
{
@@ -191,8 +235,6 @@ angular.module("umbraco.directives")
}
});
var init = function(image){
scope.loaded = false;
@@ -219,10 +261,10 @@ angular.module("umbraco.directives")
};
/// WATCHERS ////
// Watchers
scope.$watchCollection('[width, height]', function(newValues, oldValues){
//we have to reinit the whole thing if
//one of the external params changes
// We have to reinit the whole thing if
// one of the external params changes
if(newValues !== oldValues){
setDimensions($image);
setConstraints();
@@ -230,29 +272,18 @@ angular.module("umbraco.directives")
});
var throttledResizing = _.throttle(function(){
resizeImageToScale(scope.dimensions.scale.current);
resizeImageToScale(scope.dimensions.scale.current);
calculateCropBox();
}, 15);
//happens when we change the scale
scope.$watch("dimensions.scale.current", function(){
if(scope.loaded){
// Happens when we change the scale
scope.$watch("dimensions.scale.current", function (newValue, oldValue) {
if (scope.loaded) {
throttledResizing();
}
});
//ie hack
if(window.navigator.userAgent.indexOf("MSIE ") >= 0){
var ranger = element.find("input");
ranger.on("change",function(){
scope.$apply(function(){
scope.dimensions.scale.current = ranger.val();
});
});
}
//// INIT /////
// Init
$image.on("load", function(){
$timeout(function(){
init($image);
@@ -12,6 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) {
searchFromName: "@",
showSearch: "@",
section: "@",
ignoreUserStartNodes: "@",
hideSearchCallback: "=",
searchCallback: "="
},
@@ -34,6 +35,7 @@ function treeSearchBox(localizationService, searchService, $q) {
scope.showSearch = "false";
}
//used to cancel any request in progress if another one needs to take it's place
var canceler = null;
@@ -60,6 +62,11 @@ function treeSearchBox(localizationService, searchService, $q) {
searchArgs["searchFrom"] = scope.searchFromId;
}
//append ignoreUserStartNodes value if there is one
if (scope.ignoreUserStartNodes) {
searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes;
}
searcher(searchArgs).then(function (data) {
scope.searchCallback(data);
//set back to null so it can be re-created
@@ -60,6 +60,7 @@ function confirmDirective() {
link: function (scope, element, attr, ctrl) {
scope.showCancel = false;
scope.showConfirm = false;
scope.confirmButtonState = "init";
if (scope.onConfirm) {
scope.showConfirm = true;
@@ -68,6 +69,15 @@ function confirmDirective() {
if (scope.onCancel) {
scope.showCancel = true;
}
scope.confirm = function () {
if (!scope.onConfirm) {
return;
}
scope.confirmButtonState = "busy";
scope.onConfirm();
}
}
};
}
@@ -91,6 +91,10 @@ Use this directive to generate a pagination.
function link(scope, el, attr, ctrl) {
function activate() {
// page number is sometimes a string - let's make sure it's an int before we do anything with it
if (scope.pageNumber) {
scope.pageNumber = parseInt(scope.pageNumber);
}
scope.pagination = [];
@@ -1,36 +0,0 @@
(function () {
'use strict';
function PermissionDirective() {
function link(scope, el, attr, ctrl) {
scope.change = function() {
scope.selected = !scope.selected;
if(scope.onChange) {
scope.onChange({'selected': scope.selected});
}
};
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/users/umb-permission.html',
scope: {
name: "=",
description: "=?",
selected: "=",
onChange: "&"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbPermission', PermissionDirective);
})();
@@ -59,20 +59,27 @@
}
//A 401 means that the user is not logged in
if (rejection.status === 401 && !rejection.config.url.endsWith("umbraco/backoffice/UmbracoApi/Authentication/GetCurrentUser")) {
if (rejection.status === 401) {
//avoid an infinite loop
var umbRequestHelper = $injector.get('umbRequestHelper');
var getCurrentUserPath = umbRequestHelper.getApiUrl("authenticationApiBaseUrl", "GetCurrentUser");
if (!rejection.config.url.endsWith(getCurrentUserPath)) {
var userService = $injector.get('userService'); // see above
var userService = $injector.get('userService'); // see above
//Associate the user name with the retry to ensure we retry for the right user
return userService.getCurrentUser()
.then(function (user) {
var userName = user ? user.name : null;
//The request bounced because it was not authorized - add a new request to the retry queue
return requestRetryQueue.pushRetryFn('unauthorized-server', userName, function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(rejection.config);
//Associate the user name with the retry to ensure we retry for the right user
return userService.getCurrentUser()
.then(function(user) {
var userName = user ? user.name : null;
//The request bounced because it was not authorized - add a new request to the retry queue
return requestRetryQueue.pushRetryFn('unauthorized-server',
userName,
function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(rejection.config);
});
});
});
}
}
else if (rejection.status === 404) {
@@ -215,14 +215,6 @@ angular.module('umbraco.mocks').
"placeholders_nameentity": "Name the %0%...",
"placeholders_search": "Type to search...",
"placeholders_filter": "Type to filter...",
"editcontenttype_allowedchildnodetypes": "Allowed child nodetypes",
"editcontenttype_create": "Create",
"editcontenttype_deletetab": "Delete tab",
"editcontenttype_description": "Description",
"editcontenttype_newtab": "New tab",
"editcontenttype_tab": "Tab",
"editcontenttype_thumbnail": "Thumbnail",
"editcontenttype_iscontainercontenttype": "Use as container content type",
"editdatatype_addPrevalue": "Add prevalue",
"editdatatype_dataBaseDatatype": "Database datatype",
"editdatatype_guid": "Property editor GUID",
@@ -365,17 +365,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* </pre>
*
* @param {Int} id id of content item to return
* @param {Int} culture optional culture to retrieve the item in
* @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to
* @returns {Promise} resourcePromise object containing the content item.
*
*/
getById: function (id) {
getById: function (id, options) {
var defaults = {
ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetById",
{ id: id })),
[{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])),
'Failed to retrieve data for content id ' + id)
.then(function (result) {
return $q.when(umbDataFormatter.formatContentGetData(result));
@@ -344,6 +344,12 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "Import", { file: file })),
"Failed to import document type " + file
);
},
createDefaultTemplate: function (id) {
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })),
'Failed to create default template for content type with id ' + id);
}
};
}
@@ -284,15 +284,31 @@ function entityResource($q, $http, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the entity.
*
*/
getAncestors: function (id, type, culture) {
getAncestors: function (id, type, culture, options) {
var defaults = {
ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
if (culture === undefined) culture = "";
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetAncestors",
[{ id: id }, { type: type }, { culture: culture }])),
'Failed to retrieve ancestor data for id ' + id);
[
{ id: id },
{ type: type },
{ culture: culture },
{ ignoreUserStartNodes: options.ignoreUserStartNodes }
])),
'Failed to retrieve ancestor data for id ' + id);
},
/**
@@ -424,7 +440,8 @@ function entityResource($q, $http, umbRequestHelper) {
pageNumber: 1,
filter: '',
orderDirection: "Ascending",
orderBy: "SortOrder"
orderBy: "SortOrder",
ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
@@ -453,7 +470,8 @@ function entityResource($q, $http, umbRequestHelper) {
pageSize: options.pageSize,
orderBy: options.orderBy,
orderDirection: options.orderDirection,
filter: encodeURIComponent(options.filter)
filter: encodeURIComponent(options.filter),
ignoreUserStartNodes: options.ignoreUserStartNodes
}
)),
'Failed to retrieve child data for id ' + parentId);
@@ -481,12 +499,19 @@ function entityResource($q, $http, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the entity array.
*
*/
search: function (query, type, searchFrom, canceler) {
search: function (query, type, options, canceler) {
var args = [{ query: query }, { type: type }];
if (searchFrom) {
args.push({ searchFrom: searchFrom });
if(options !== undefined) {
if (options.searchFrom) {
args.push({ searchFrom: options.searchFrom });
}
if (options.ignoreUserStartNodes) {
args.push({ ignoreUserStartNodes: options.ignoreUserStartNodes });
}
}
var httpConfig = {};
if (canceler) {
@@ -53,7 +53,7 @@ function logResource($q, $http, umbRequestHelper) {
*
* @param {Object} options options object
* @param {Int} options.id the id of the entity
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10
* @param {Int} options.pageNumber if paging data, current page index, default = 1
* @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending`
* @param {Date} options.sinceDate if provided this will only get log entries going back to this date
@@ -122,7 +122,7 @@ function logResource($q, $http, umbRequestHelper) {
* </pre>
*
* @param {Object} options options object
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10
* @param {Int} options.pageNumber if paging data, current page index, default = 1
* @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending`
* @param {Date} options.sinceDate if provided this will only get log entries going back to this date
@@ -329,7 +329,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
filter: '',
orderDirection: "Ascending",
orderBy: "SortOrder",
orderBySystemField: true
orderBySystemField: true,
ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
@@ -367,6 +368,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"GetChildren",
[
{ id: parentId },
{ ignoreUserStartNodes: options.ignoreUserStartNodes },
{ pageNumber: options.pageNumber },
{ pageSize: options.pageSize },
{ orderBy: options.orderBy },
@@ -112,6 +112,7 @@ When building a custom infinite editor view you can use the same components as a
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
@@ -201,7 +202,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @param {Object} editor rendering options
* @param {String} editor.view Path to view
* @param {String} editor.size Sets the size of the editor ("Small"). If nothing is set it will use full width.
* @param {String} editor.size Sets the size of the editor ("small"). If nothing is set it will use full width.
*/
function open(editor) {
@@ -465,6 +466,8 @@ When building a custom infinite editor view you can use the same components as a
* @description
* Opens an icon picker in infinite editing, the submit callback returns the selected icon
* @param {Object} editor rendering options
* @param {String} editor.icon The CSS class representing the icon - eg. "icon-autofill".
* @param {String} editor.color The CSS class representing the color - eg. "color-red".
* @param {Callback} editor.submit Submits the editor
* @param {Callback} editor.close Closes the editor
* @returns {Object} editor object
@@ -45,7 +45,7 @@
(function () {
'use strict';
function listViewHelper(localStorageService) {
function listViewHelper($location, localStorageService, urlHelper) {
var firstSelectedIndex = 0;
var localStorageKey = "umblistViewLayout";
@@ -559,6 +559,32 @@
}
/**
* @ngdoc method
* @name umbraco.services.listViewHelper#editItem
* @methodOf umbraco.services.listViewHelper
*
* @description
* Method for opening an item in a list view for editing.
*
* @param {Object} item The item to edit
*/
function editItem(item) {
if (!item.editPath) {
return;
}
var parts = item.editPath.split("?");
var path = parts[0];
var params = parts[1]
? urlHelper.getQueryStringParams("?" + parts[1])
: {};
$location.path(path);
for (var p in params) {
$location.search(p, params[p]);
}
}
function isMatchingLayout(id, layout) {
// legacy format uses "nodeId", be sure to look for both
return layout.id === id || layout.nodeId === id;
@@ -579,7 +605,8 @@
isSelectedAll: isSelectedAll,
setSortingDirection: setSortingDirection,
setSorting: setSorting,
getButtonPermissions: getButtonPermissions
getButtonPermissions: getButtonPermissions,
editItem: editItem
};
@@ -42,7 +42,11 @@ angular.module('umbraco.services')
throw "args.term is required";
}
return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) {
var options = {
searchFrom: args.searchFrom
}
return entityResource.search(args.term, "Member", options).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMemberResult(item);
});
@@ -67,7 +71,12 @@ angular.module('umbraco.services')
throw "args.term is required";
}
return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) {
var options = {
searchFrom: args.searchFrom,
ignoreUserStartNodes: args.ignoreUserStartNodes
}
return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureContentResult(item);
});
@@ -92,7 +101,12 @@ angular.module('umbraco.services')
throw "args.term is required";
}
return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) {
var options = {
searchFrom: args.searchFrom,
ignoreUserStartNodes: args.ignoreUserStartNodes
}
return entityResource.search(args.term, "Media", options).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMediaResult(item);
});
@@ -48,7 +48,14 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
if (configuredStylesheets) {
angular.forEach(configuredStylesheets, function (val, key) {
stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css");
if (val.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/") === 0) {
// current format (full path to stylesheet)
stylesheets.push(val);
}
else {
// legacy format (stylesheet name only) - must prefix with stylesheet folder and postfix with ".css"
stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css");
}
promises.push(stylesheetResource.getRulesByName(val).then(function (rules) {
angular.forEach(rules, function (rule) {
@@ -446,6 +453,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
}
editor.dom.setAttrib(imgElm, 'id', null);
editor.fire('Change');
}, 500);
}
},
@@ -995,9 +1005,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
return;
}
// Is email and not //user@domain.com
if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf('mailto:') === -1) {
href = 'mailto:' + href;
// Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified
if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) {
// assume it's a mailto link
href = 'mailto:' + href;
insertLink();
return;
}
@@ -1143,11 +1154,25 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
let self = this;
function getIgnoreUserStartNodes(args) {
var ignoreUserStartNodes = false;
// Most property editors have a "config" property with ignoreUserStartNodes on then
if (args.model.config) {
ignoreUserStartNodes = Object.toBoolean(args.model.config.ignoreUserStartNodes);
}
// EXCEPT for the grid's TinyMCE editor, that one wants to be special and the config is called "configuration" instead
else if (args.model.configuration) {
ignoreUserStartNodes = Object.toBoolean(args.model.configuration.ignoreUserStartNodes);
}
return ignoreUserStartNodes;
}
//create link picker
self.createLinkPicker(args.editor, function (currentTarget, anchorElement) {
var linkPicker = {
currentTarget: currentTarget,
anchors: editorState.current ? self.getAnchorNames(JSON.stringify(editorState.current.properties)) : [],
ignoreUserStartNodes: getIgnoreUserStartNodes(args),
submit: function (model) {
self.insertLinkInEditor(args.editor, model.target, anchorElement);
editorService.close();
@@ -1161,13 +1186,25 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
//Create the insert media plugin
self.createMediaPicker(args.editor, function (currentTarget, userData) {
var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
var startNodeIsVirtual = userData.startMediaIds.length !== 1;
var ignoreUserStartNodes = getIgnoreUserStartNodes(args);
if (ignoreUserStartNodes) {
ignoreUserStartNodes = true;
startNodeId = -1;
startNodeIsVirtual = true;
}
var mediaPicker = {
currentTarget: currentTarget,
onlyImages: true,
showDetails: true,
disableFolderSelect: true,
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
ignoreUserStartNodes: ignoreUserStartNodes,
submit: function (model) {
self.insertMediaInEditor(args.editor, model.selection[0]);
editorService.close();
@@ -1224,6 +1261,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
view: 'views/propertyeditors/rte/codeeditor.html',
submit: function (model) {
args.editor.setContent(model.content);
args.editor.fire('Change');
editorService.close();
},
close: function () {
@@ -29,6 +29,15 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
return cacheKey;
}
// Adapted from: https://stackoverflow.com/a/2140723
// Please note, we can NOT test this functionality correctly in Phantom because it implements
// the localeCompare method incorrectly: https://github.com/ariya/phantomjs/issues/11063
function invariantEquals(a, b) {
return typeof a === "string" && typeof b === "string"
? a.localeCompare(b, undefined, { sensitivity: "base" }) === 0
: a === b;
}
return {
/** Internal method to return the tree cache */
@@ -165,8 +174,8 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS
Umbraco.Sys.ServerVariables.umbracoPlugins.trees &&
angular.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) {
var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function(item) {
return item.alias === treeAlias;
var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function (item) {
return invariantEquals(item.alias, treeAlias);
});
return found ? found.packageFolder : undefined;
@@ -384,6 +384,8 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
// Get the filename from the x-filename header or default to "download.bin"
var filename = headers['x-filename'] || 'download.bin';
filename = decodeURIComponent(filename);
// Determine the content type from the header or default to "application/octet-stream"
var contentType = headers['content-type'] || octetStreamMime;
@@ -18,7 +18,27 @@ function MainController($scope, $location, appState, treeService, notificationsS
$scope.drawer = {};
$scope.search = {};
$scope.login = {};
$scope.tabbingActive = false;
// There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution.
// For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2
function handleFirstTab(evt) {
if (evt.keyCode === 9) {
$scope.tabbingActive = true;
window.removeEventListener('keydown', handleFirstTab);
window.addEventListener('mousedown', disableTabbingActive);
}
}
function disableTabbingActive(evt) {
$scope.tabbingActive = false;
window.removeEventListener('mousedown', disableTabbingActive);
window.addEventListener("keydown", handleFirstTab);
}
window.addEventListener("keydown", handleFirstTab);
$scope.removeNotification = function (index) {
notificationsService.remove(index);
};
@@ -0,0 +1,16 @@
.umb-outline {
&:focus {
outline:none;
.tabbing-active &::after {
content: '';
position: absolute;
z-index: 10000;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 3px;
box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid;
}
}
}
@@ -59,6 +59,7 @@
@import "application/grid.less";
@import "rte.less";
@import "application/shadows.less";
@import "application/umb-outline.less";
@import "application/animations.less";
// Utilities
@@ -156,11 +157,15 @@
@import "components/umb-number-badge.less";
@import "components/umb-progress-circle.less";
@import "components/umb-stylesheet.less";
@import "components/umb-textstring.less";
@import "components/umb-textarea.less";
@import "components/umb-dropdown.less";
@import "components/umb-range-slider.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";
@import "components/buttons/umb-toggle.less";
@import "components/buttons/umb-toggle-group.less";
@import "components/notifications/umb-notifications.less";
@import "components/umb-file-dropzone.less";
@@ -175,7 +180,6 @@
@import "components/users/umb-user-group-preview.less";
@import "components/users/umb-user-preview.less";
@import "components/users/umb-user-picker-list.less";
@import "components/users/umb-permission.less";
// Utilities
@@ -25,12 +25,27 @@
height: @appHeaderHeight;
}
.umb-app-header__action a:hover,
.umb-app-header__action a:focus {
.umb-app-header__action a {
outline: none;
&:focus {
.tabbing-active & {
.umb-app-header__action-icon::after {
content: '';
position: absolute;
z-index:10000;
top: -7px;
left: -7px;
width: 36px;
height: 35px;
border-radius: 3px;
box-shadow: 0 0 2px @pinkLight, inset 0 0 2px 1px @pinkLight;
}
}
}
}
.umb-app-header__action-icon {
position: relative;
opacity: 0.8;
color: @white;
font-size: 22px;
@@ -74,6 +74,9 @@
font-size: 19px;
color: @gray-7;
cursor: pointer;
background: transparent;
padding: 0;
border: none;
}
.umb-tour-step__close:hover,
@@ -3,14 +3,25 @@
display: inline-block;
}
.umb-button__button:focus {
outline: none;
}
.umb-button__button {
position: relative;
}
.umb-button__button:focus {
outline: none;
.tabbing-active &:after {
content: '';
position: absolute;
z-index: 10000;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 3px;
box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid;
}
}
.umb-button__content {
opacity: 1;
transition: opacity 0.25s ease;
@@ -137,4 +148,4 @@
.umb-button--block {
display: block;
width: 100%;
}
}
@@ -0,0 +1,35 @@
.umb-toggle-group {
.umb-toggle-group-item {
display: flex;
border-bottom: 1px solid @gray-9;
padding: 7px 0;
}
.umb-toggle-group-item:last-of-type {
border-bottom: none;
}
.umb-toggle-group-item__toggle {
padding-right: 20px;
cursor: pointer;
}
.umb-toggle-group-item__content {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1 1 auto;
cursor: pointer;
}
.umb-toggle-group-item--disabled .umb-toggle-group-item__toggle,
.umb-toggle-group-item--disabled .umb-toggle-group-item__content {
cursor: not-allowed;
opacity: 0.8;
}
.umb-toggle-group-item__description {
font-size: 13px;
color: @gray-4;
}
}
@@ -14,9 +14,9 @@
cursor: pointer;
align-items: center;
display: flex;
width: 48px;
height: 24px;
border-radius: 3px;
width: 38px;
height: 18px;
border-radius: 10px;
border: 1px solid @inputBorder;
background-color: @inputBorder;
position: relative;
@@ -49,14 +49,14 @@
top: 1px;
left: 1px;
display: block;
width: 22px;
height: 22px;
width: 16px;
height: 16px;
background-color: @white;
border-radius: 2px;
border-radius: 8px;
transition: transform 120ms ease-in-out, background-color 120ms;
.umb-toggle.umb-toggle--checked & {
transform: translateX(24px);
transform: translateX(20px);
background-color: white;
}
@@ -67,20 +67,22 @@
.umb-toggle__icon {
position: absolute;
font-size: 12px;
line-height: 1em;
text-decoration: none;
transition: all 0.2s ease;
}
.umb-toggle__icon--left {
left: 6px;
color: @ui-btn;
transition: color 120ms;
left: 5px;
color: white;
transition: opacity 120ms;
opacity: 0;
// .umb-toggle:hover & {
// color: @ui-btn-hover;
// }
.umb-toggle--checked & {
color: white;
opacity: 1;
}
.umb-toggle.umb-toggle--checked:hover & {
color: white;
@@ -90,6 +92,10 @@
.umb-toggle__icon--right {
right: 5px;
color: @ui-btn;
transition: opacity 120ms;
.umb-toggle--checked & {
opacity: 0;
}
.umb-toggle:hover & {
color: @ui-btn-hover;
}
@@ -90,6 +90,9 @@
.umb-editor-header__name-and-description {
margin-right: 20px;
.umb-panel-header-description {
padding: 0 10px;
}
}
.-split-view-active .umb-editor-header__name-and-description {
@@ -108,7 +111,7 @@ input.umb-editor-header__name-input {
margin-bottom: 0;
font-weight: bold;
box-sizing: border-box;
height: 30px;
height: 32px;
line-height: 32px;
width: 100%;
padding: 0 10px;
@@ -308,7 +311,7 @@ a.umb-variant-switcher__toggle {
padding: 10px 20px;
background: @white;
border-top: 1px solid @gray-9;
z-index: 1;
z-index: 30;
bottom: 0;
display: flex;
align-items: center;
@@ -19,11 +19,16 @@
background: @brownGrayLight;
}
}
.umb-editor-sub-header--white {
background-color: white;
border-color: white;
}
.umb-editor-sub-header.--state-selection {
padding-left: 10px;
padding-right: 10px;
background-color: @pinkLight;
border-color: @pinkLight;
border-radius: 3px;
}
@@ -1,7 +1,7 @@
.umb-expansion-panel {
background: @white;
border-radius: 3px;
margin-bottom: 16px;
margin-bottom: 20px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.16);
}
@@ -12,7 +12,6 @@
}
a, a:hover {
outline: none;
text-decoration: none;
}
@@ -228,40 +227,64 @@ body.touch .umb-tree {
}
}
.umb-tree-item__annotation {
&::before {
font-family: 'icomoon';
position: absolute;
bottom: 0;
}
}
.has-unpublished-version, .is-container, .protected {
> .umb-tree-item__inner {
> .umb-tree-item__annotation {
background-color: @white;
border-radius: 50%;
width: 12px;
height: 12px;
position: absolute;
margin-left: 12px;
top: 17px;
.has-unpublished-version > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e25a";
color: @green;
font-size: 20px;
margin-left: -25px;
&::before {
font-family: 'icomoon';
position: absolute;
top: -4px;
}
}
&:hover > .umb-tree-item__annotation {
background-color: @ui-option-hover;
}
}
&.current > .umb-tree-item__inner > .umb-tree-item__annotation {
background-color: @pinkLight;
}
}
.is-container > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e04e";
color: @blue;
font-size: 9px;
margin-left: -20px;
margin-left: 2px;
left: 0px;
}
.has-unpublished-version > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e25a";
color: @green;
font-size: 23px;
margin-left: 16px;
left: -21px;
}
.protected > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e256";
color: @red;
font-size: 20px;
margin-left: -25px;
font-size: 23px;
margin-left: -3px;
left: -2px;
}
.locked > .umb-tree-item__inner > .umb-tree-item__annotation::before {
content: "\e0a7";
color: @red;
font-size: 9px;
margin-left: -20px;
margin-left: 2px;
left: 0px;
}
.no-access {
@@ -1,7 +1,7 @@
.umb-box {
background: @white;
border-radius: 3px;
margin-bottom: 8px;
margin-bottom: 20px;
box-shadow: 0 1px 1px 0 rgba(0,0,0,.16);
}
@@ -28,4 +28,4 @@
.umb-box-content {
padding: 20px;
}
}
@@ -2,19 +2,21 @@
border: 2px solid @white;
width: 25px;
height: 25px;
background: @gray-7;
border-radius: 50%;
border: 1px solid @gray-7;
border-radius: 3px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
color: @white;
color: @gray-7;
cursor: pointer;
font-size: 15px;
}
.umb-checkmark--checked {
background: @green;
background: @ui-active;
border-color: @ui-active;
color: @white;
}
.umb-checkmark--xs {
@@ -0,0 +1,3 @@
.umb-dropdown {
.umb-property-editor--limit-width();
}
@@ -16,6 +16,7 @@
margin: 0 0 0 26px;
position: relative;
top: 0;
user-select: none;
}
&__input {
@@ -23,14 +24,27 @@
top: 0;
left: 0;
opacity: 0;
&:hover ~ .umb-form-check__state .umb-form-check__check {
border-color: @inputBorderFocus;
}
&:checked ~ .umb-form-check__state .umb-form-check__check {
border-color: @ui-option-type;
background-color: @ui-option-type;
}
&:checked:hover ~ .umb-form-check__state .umb-form-check__check {
&::before {
background: @ui-option-type-hover;
}
}
&:focus:checked ~ .umb-form-check .umb-form-check__check,
&:focus ~ .umb-form-check__state .umb-form-check__check {
border-color: @inputBorderFocus;
.tabbing-active & {
outline: 2px solid @inputBorderTabFocus;
}
}
&:checked ~ .umb-form-check__state {
@@ -611,6 +611,10 @@
padding-bottom: 8px;
padding-left: 6px;
line-height: inherit;
.mce-caret {
margin-top: 6px;
}
}
&:not(.mce-menubtn) {
@@ -75,7 +75,7 @@
display: flex;
align-items: center;
border-bottom: 1px solid @gray-9;
padding: 10px 15px;
padding: 10px 15px 10px 10px;
}
.umb-group-builder__group-title {
@@ -112,7 +112,7 @@ input.umb-group-builder__group-title-input {
}
input.umb-group-builder__group-title-input:disabled:hover {
border: none;
border-color: transparent;
}
.umb-group-builder__group-title-input:hover {
@@ -135,12 +135,35 @@ input.umb-group-builder__group-title-input:disabled:hover {
margin-left: auto;
}
.umb-group-builder__group-add-property {
min-height: 46px;
margin-right: 45px;
margin-left: 270px;
border-radius: 3px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border: 1px dashed @gray-7;
background-color: transparent;
color: @ui-action-type;
font-weight: bold;
position: relative;
&:hover {
color:@ui-action-type-hover;
text-decoration: none;
border-color: @ui-active-type-hover;
}
}
/* ---------- PROPERTIES ---------- */
.umb-group-builder__properties {
list-style: none;
margin: 0;
padding: 15px;
padding-right: 5px;
min-height: 35px; // the height of a sortable property
}
@@ -228,6 +251,9 @@ input.umb-group-builder__group-title-input:disabled:hover {
overflow: hidden;
border-color: transparent;
background: transparent;
&:focus {
border-color: @inputBorderFocus;
}
}
.umb-group-builder__property-meta-label textarea.ng-invalid {
@@ -247,6 +273,9 @@ input.umb-group-builder__group-title-input:disabled:hover {
overflow: hidden;
border-color: transparent;
background: transparent;
&:focus {
border-color: @inputBorderFocus;
}
}
.umb-group-builder__property-preview {
@@ -265,21 +294,17 @@ input.umb-group-builder__group-title-input:disabled:hover {
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.1);
background: rgba(225,225,225,.5);
transition: opacity 120ms;
}
}
.umb-group-builder__property-preview:hover {
.umb-group-builder__property-preview:not(.-not-clickable):hover {
&::after {
opacity: .8;
opacity: .66;
}
}
.umb-group-builder__property-preview:focus {
outline: none;
}
.umb-group-builder__property-preview.-not-clickable {
cursor: auto;
}
@@ -301,23 +326,54 @@ input.umb-group-builder__group-title-input:disabled:hover {
opacity: 0.8
}
.umb-group-builder__open-settings {
position: absolute;
z-index:1;
top: 0;
bottom:0;
left: 0;
width: 100%;
background-color: transparent;
border: none;
&:focus {
outline:0;
border: 1px solid @inputBorderFocus;
}
}
.umb-group-builder__property-actions {
flex: 0 0 40px;
text-align: center;
margin: 15px 0 0 15px;
flex: 0 0 44px;
text-align: right;
margin-top: 7px;
}
.umb-group-builder__property-action {
margin: 0 0 10px 0;
display: block;
font-size: 18px;
position: relative;
cursor: pointer;
color: @ui-icon;
}
.umb-group-builder__property-action:hover {
color: @ui-icon-hover;
position: relative;
margin: 5px 0;
> button {
border: none;
font-size: 18px;
position: relative;
cursor: pointer;
color: @ui-icon;
margin: 0;
padding: 5px 10px;
width: auto;
overflow: visible;
background: transparent;
line-height: normal;
outline: 0;
-webkit-appearance: none;
&:hover, &:focus {
color: @ui-icon-hover;
}
}
}
.umb-group-builder__property-tags {
@@ -439,7 +495,7 @@ input.umb-group-builder__group-sort-value {
font-size: 14px;
color: @ui-action-type;
&:hover {
&:hover{
text-decoration: none;
color:@ui-action-type-hover;
border-color:@ui-action-border-hover;
@@ -31,6 +31,11 @@ a.umb-list-item:focus {
opacity: 1;
}
.umb-list-item__description {
font-size: 13px;
color: @gray-4;
}
.umb-list-checkbox {
position: absolute;
opacity: 0;
@@ -27,17 +27,12 @@
.umb-nested-content__item {
position: relative;
text-align: left;
border-top: solid 1px transparent;
background: @white;
}
.umb-nested-content__item--active:not(.umb-nested-content__item--single) {
background: @gray-10;
}
.umb-nested-content__item.ui-sortable-placeholder {
background: @gray-10;
border: 1px dashed @gray-8;
border: 1px solid @gray-9;
visibility: visible !important;
height: 55px;
margin-top: -1px;
@@ -52,9 +47,7 @@
}
.umb-nested-content__header-bar {
padding: 15px 20px;
border-bottom: 1px dashed @gray-8;
text-align: right;
border-bottom: 1px solid @gray-9;
cursor: pointer;
background-color: @white;
@@ -68,25 +61,28 @@
.umb-nested-content__heading {
line-height: 20px;
position: relative;
&.-with-icon {
padding-left: 20px;
margin-top:1px;
padding: 15px 20px;
color:@ui-option-type;
border-radius: 3px 3px 0 0;
&:hover {
color:@ui-option-type-hover;
}
i {
color: @gray-2;
position: absolute;
left: 0;
display: inline;
margin-right: 10px;
}
.umb-nested-content__item-name {
display: inline;
max-height: 20px;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
}
.umb-nested-content__icons {
@@ -110,12 +106,30 @@
}
}
.umb-nested-content__item--active > .umb-nested-content__header-bar {
.umb-nested-content__heading {
background-color: @ui-active;
&:hover {
color:@ui-option-type;
}
}
.umb-nested-content__icons {
background-color: @ui-active;
&:before {
background: linear-gradient(90deg, rgba(255,255,255,0), @ui-active);
}
}
}
.umb-nested-content__header-bar:hover .umb-nested-content__icons,
.umb-nested-content__item--active > .umb-nested-content__header-bar .umb-nested-content__icons {
opacity: 1;
}
.umb-nested-content__icon,
.umb-nested-content__icon.umb-nested-content__icon--disabled:hover {
display: inline-block;
@@ -128,6 +142,10 @@
text-decoration: none !important;
}
.umb-nested-content__icon.umb-nested-content__icon--disabled:hover {
cursor: default;
}
.umb-nested-content__icon:hover,
.umb-nested-content__icon--active
{
@@ -154,13 +172,47 @@
}
.umb-nested-content__footer-bar {
text-align: center;
padding-top: 20px;
margin-top: 20px;
}
.umb-nested-content__add-content {
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed @ui-action-discreet-border;
color: @ui-action-discreet-type;
font-weight: bold;
padding: 5px 15px;
box-sizing: border-box;
}
.umb-nested-content__add-content:hover {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-border-hover;
text-decoration: none;
}
.umb-nested-content__add-content.--disabled,
.umb-nested-content__add-content.--disabled:hover {
color: @gray-7;
border-color: @gray-7;
cursor: default;
}
.umb-nested-content__content {
border-bottom: 1px dashed @gray-8;
border-top: 1px solid transparent;
border-bottom: 1px solid @gray-9;
border-left: 1px solid @gray-9;
border-right: 1px solid @gray-9;
border-radius: 0 0 3px 3px;
}
.umb-nested-content__item--active:not(.umb-nested-content__item--single) .umb-nested-content__content {
background: @brownGrayExtraLight;
}
.umb-nested-content__content .umb-control-group {
@@ -6,7 +6,7 @@
}
.umb-editor-wrapper .umb-node-preview {
max-width: 66.6%;
.umb-property-editor--limit-width();
}
.umb-node-preview:last-of-type {
@@ -103,7 +103,7 @@
}
.umb-editor-wrapper .umb-node-preview-add {
max-width: 66.6%;
.umb-property-editor--limit-width();
}
.umb-overlay,

Some files were not shown because too many files have changed in this diff Show More