Merge branch 'v8/dev' into v8/feature/4940-save-media-reference-instead-of-media-url
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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" `
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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 });
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
+584
-114
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
+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();
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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) {
|
||||
|
||||
|
||||
+105
@@ -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);
|
||||
|
||||
})();
|
||||
+2
-2
@@ -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]);
|
||||
}
|
||||
|
||||
+5
-3
@@ -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) {
|
||||
|
||||
+3
@@ -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'
|
||||
};
|
||||
|
||||
|
||||
+60
-29
@@ -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);
|
||||
|
||||
+7
@@ -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 = [];
|
||||
|
||||
|
||||
-36
@@ -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;
|
||||
|
||||
+5
@@ -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
Reference in New Issue
Block a user