diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 124557d416..72ad5acb96 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -136,7 +136,7 @@ You can get in touch with [the core contributors team](#the-core-contributors-te In order to build the Umbraco source code locally, first make sure you have the following installed. - * [Visual Studio 2019 v16.3+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/) + * [Visual Studio 2019 v16.8+ (with .NET Core 3.0)](https://visualstudio.microsoft.com/vs/) * [Node.js v10+](https://nodejs.org/en/download/) * npm v6.4.1+ (installed with Node.js) * [Git command line](https://git-scm.com/download/) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index e215bdbf29..fe5b7db704 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -131,6 +131,9 @@ + + + @@ -153,7 +156,7 @@ - + @@ -171,6 +174,18 @@ + + + + + + + + + + + + diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index f82d90c1b2..f4ee42deeb 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -48,42 +48,60 @@ stages: vmImage: ubuntu-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x + - task: DotNetCoreCLI@2 + displayName: dotnet build + inputs: + command: build + projects: '**/umbraco-netcore-only.sln' - task: DotNetCoreCLI@2 displayName: dotnet test inputs: command: test projects: '**/*.Tests.UnitTests.csproj' + arguments: '--no-build' - job: MacOS_Unit_Tests displayName: Mac OS pool: vmImage: macOS-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x + - task: DotNetCoreCLI@2 + displayName: dotnet build + inputs: + command: build + projects: '**/umbraco-netcore-only.sln' - task: DotNetCoreCLI@2 displayName: dotnet test inputs: command: test projects: '**/*.Tests.UnitTests.csproj' + arguments: '--no-build' - job: Windows_Unit_Tests displayName: Windows pool: vmImage: windows-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x + - task: DotNetCoreCLI@2 + displayName: dotnet build + inputs: + command: build + projects: '**/umbraco.sln' - task: DotNetCoreCLI@2 displayName: dotnet test inputs: command: test projects: '**/*.Tests.UnitTests.csproj' + arguments: '--no-build' - stage: Integration_Tests displayName: Integration Tests dependsOn: [] @@ -97,14 +115,20 @@ stages: vmImage: ubuntu-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x + - task: DotNetCoreCLI@2 + displayName: dotnet build + inputs: + command: build + projects: '**/umbraco-netcore-only.sln' - task: DotNetCoreCLI@2 displayName: dotnet test inputs: command: test projects: '**/Umbraco.Tests.Integration.csproj' + arguments: '--no-build' env: UmbracoIntegrationTestConnectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);' - job: Windows_Integration_Tests @@ -114,16 +138,22 @@ stages: vmImage: windows-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x - powershell: sqllocaldb start mssqllocaldb displayName: Start MSSQL LocalDb + - task: DotNetCoreCLI@2 + displayName: dotnet build + inputs: + command: build + projects: '**/umbraco.sln' - task: DotNetCoreCLI@2 displayName: dotnet test inputs: command: test projects: '**\Umbraco.Tests.Integration*.csproj' + arguments: '--no-build' - stage: Acceptance_Tests displayName: Acceptance Tests dependsOn: [] @@ -150,7 +180,7 @@ stages: vmImage: windows-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x @@ -245,7 +275,7 @@ stages: vmImage: ubuntu-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x - task: Bash@3 @@ -335,7 +365,7 @@ stages: vmImage: windows-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x - task: NuGetToolInstaller@1 @@ -487,7 +517,7 @@ stages: vmImage: windows-latest steps: - task: UseDotNet@2 - displayName: Use .Net Core sdk 5.x + displayName: Use .Net 5.x inputs: version: 5.x - task: PowerShell@2 diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs index 974d8bd6aa..a04ece0d04 100644 --- a/src/Umbraco.Core/Cache/AppCaches.cs +++ b/src/Umbraco.Core/Cache/AppCaches.cs @@ -1,12 +1,15 @@ using System; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { /// /// Represents the application caches. /// - public class AppCaches + public class AppCaches : IDisposable { + private bool _disposedValue; + /// /// Initializes a new instance of the with cache providers. /// @@ -72,5 +75,26 @@ namespace Umbraco.Cms.Core.Cache requestCache, new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + RuntimeCache.DisposeIfDisposable(); + RequestCache.DisposeIfDisposable(); + IsolatedCaches.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs index 1be1752b22..af7c1db228 100644 --- a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs +++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { @@ -7,7 +8,7 @@ namespace Umbraco.Cms.Core.Cache /// Provides a base class for implementing a dictionary of . /// /// The type of the dictionary key. - public abstract class AppPolicedCacheDictionary + public abstract class AppPolicedCacheDictionary : IDisposable { private readonly ConcurrentDictionary _caches = new ConcurrentDictionary(); @@ -24,6 +25,7 @@ namespace Umbraco.Cms.Core.Cache /// Gets the internal cache factory, for tests only! /// private readonly Func _cacheFactory; + private bool _disposedValue; /// /// Gets or creates a cache. @@ -70,5 +72,27 @@ namespace Umbraco.Cms.Core.Cache foreach (var cache in _caches.Values) cache.Clear(); } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + foreach(var value in _caches.Values) + { + value.DisposeIfDisposable(); + } + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index 451af437b2..be8bf7bdec 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { @@ -11,8 +12,10 @@ namespace Umbraco.Cms.Core.Cache /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, /// when the item is deep-cloneable. /// - public class DeepCloneAppCache : IAppPolicyCache + public class DeepCloneAppCache : IAppPolicyCache, IDisposable { + private bool _disposedValue; + /// /// Initializes a new instance of the class. /// @@ -152,5 +155,24 @@ namespace Umbraco.Cms.Core.Cache return input; } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + InnerCache.DisposeIfDisposable(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index b5103f4338..a848e7cb30 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -11,9 +11,10 @@ namespace Umbraco.Cms.Core.Cache /// /// Implements on top of a . /// - public class ObjectCacheAppCache : IAppPolicyCache + public class ObjectCacheAppCache : IAppPolicyCache, IDisposable { private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + private bool _disposedValue; /// /// Initializes a new instance of the . @@ -344,5 +345,23 @@ namespace Umbraco.Cms.Core.Cache return policy; } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 066e70483a..1d66e874be 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Editors; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media.EmbedProviders; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; @@ -204,6 +205,13 @@ namespace Umbraco.Cms.Core.DependencyInjection public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + /// + /// Gets the map definitions collection builder. + /// + /// The builder. + public static MapDefinitionCollectionBuilder MapDefinitions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + /// /// Gets the data editor collection builder. /// diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index a82f2278af..1d7fb202ae 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; namespace Umbraco.Cms.Core.Models { @@ -17,8 +16,6 @@ namespace Umbraco.Cms.Core.Models // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - public PropertyGroupCollection() { } @@ -70,47 +67,37 @@ namespace Umbraco.Cms.Core.Models public new void Add(PropertyGroup item) { - try + //Note this is done to ensure existing groups can be renamed + if (item.HasIdentity && item.Id > 0) { - _addLocker.EnterWriteLock(); - - //Note this is done to ensure existing groups can be renamed - if (item.HasIdentity && item.Id > 0) + var exists = Contains(item.Id); + if (exists) { - var exists = Contains(item.Id); + var keyExists = Contains(item.Name); + 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; + } + } + else + { + var key = GetKeyForItem(item); + if (key != null) + { + var exists = Contains(key); if (exists) { - var keyExists = Contains(item.Name); - 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); + SetItem(IndexOfKey(key), item); return; } } - else - { - var key = GetKeyForItem(item); - if (key != null) - { - 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); - } - finally - { - if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); } + //collection events will be raised in InsertItem + base.Add(item); } /// diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index df4e3c65e7..f63e23ae02 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -18,10 +18,6 @@ namespace Umbraco.Cms.Core.Models // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable, ICollection { - [IgnoreDataMember] - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - - public PropertyTypeCollection(bool supportsPublishing) { SupportsPublishing = supportsPublishing; @@ -90,36 +86,28 @@ namespace Umbraco.Cms.Core.Models item.SupportsPublishing = SupportsPublishing; // TODO: this is not pretty and should be refactored - try - { - _addLocker.EnterWriteLock(); - var key = GetKeyForItem(item); - if (key != null) - { - var exists = Contains(key); - if (exists) - { - //collection events will be raised in SetItem - SetItem(IndexOfKey(key), item); - return; - } - } - //check if the item's sort order is already in use - if (this.Any(x => x.SortOrder == item.SortOrder)) - { - //make it the next iteration - item.SortOrder = this.Max(x => x.SortOrder) + 1; - } - - //collection events will be raised in InsertItem - base.Add(item); - } - finally + var key = GetKeyForItem(item); + if (key != null) { - if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); + var exists = Contains(key); + if (exists) + { + //collection events will be raised in SetItem + SetItem(IndexOfKey(key), item); + return; + } } + + //check if the item's sort order is already in use + if (this.Any(x => x.SortOrder == item.SortOrder)) + { + //make it the next iteration + item.SortOrder = this.Max(x => x.SortOrder) + 1; + } + + //collection events will be raised in InsertItem + base.Add(item); } /// diff --git a/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs index dc7f080acf..d8bade11e1 100644 --- a/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs +++ b/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs @@ -14,6 +14,11 @@ namespace Umbraco.Cms.Core.PropertyEditors //Only compress non published content that supports publishing and the property is text return true; } + if (propertyType.ValueStorageType == ValueStorageType.Integer && Constants.PropertyEditors.Aliases.Boolean.Equals(dataEditor.Alias)) + { + //Compress boolean values from int to bool + return true; + } return false; } } diff --git a/src/Umbraco.Core/Routing/SiteDomainMapper.cs b/src/Umbraco.Core/Routing/SiteDomainMapper.cs index d10ee8fc10..8583784369 100644 --- a/src/Umbraco.Core/Routing/SiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/SiteDomainMapper.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using Umbraco.Extensions; +using System.ComponentModel; namespace Umbraco.Cms.Core.Routing { @@ -364,6 +365,7 @@ namespace Umbraco.Cms.Core.Routing return ret; } + #endregion protected virtual void Dispose(bool disposing) @@ -386,5 +388,7 @@ namespace Umbraco.Cms.Core.Routing // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); } + + } } diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index 7473ce1bab..5de38bb5f2 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -3,7 +3,7 @@ netstandard2.0 Umbraco.Cms.Infrastructure.Examine Umbraco CMS - Umbraco.Examine.Lucene + Umbraco.Examine.Lucene Umbraco.Cms.Examine.Lucene @@ -20,10 +20,6 @@ - - - - @@ -38,4 +34,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs b/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs index 0d341d1d9b..7ae567739e 100644 --- a/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs +++ b/src/Umbraco.Infrastructure/Examine/UmbracoExamineExtensions.cs @@ -15,7 +15,7 @@ namespace Umbraco.Extensions /// /// myFieldName_en-us will match the "en-us" /// - internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^(?[_\\w]+)_(?[a-z]{2,3}(-[a-z0-9]{2,4})?)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); //TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression @@ -33,7 +33,7 @@ namespace Umbraco.Extensions foreach (var field in allFields) { var match = CultureIsoCodeFieldNameMatchExpression.Match(field); - if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value)) + if (match.Success && culture.InvariantEquals(match.Groups["CultureName"].Value)) { results.Add(field); } @@ -55,7 +55,7 @@ namespace Umbraco.Extensions foreach (var field in allFields) { var match = CultureIsoCodeFieldNameMatchExpression.Match(field); - if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value)) + if (match.Success && culture.InvariantEquals(match.Groups["CultureName"].Value)) { yield return field; //matches this culture field } diff --git a/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs b/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs index d278e95bb2..912ae75460 100644 --- a/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs +++ b/src/Umbraco.Infrastructure/Examine/UmbracoFieldDefinitionCollection.cs @@ -71,13 +71,13 @@ namespace Umbraco.Cms.Infrastructure.Examine return true; //before we use regex to match do some faster simple matching since this is going to execute quite a lot - if (!fieldName.Contains("_") || !fieldName.Contains("-")) + if (!fieldName.Contains("_")) return false; var match = UmbracoExamineExtensions.CultureIsoCodeFieldNameMatchExpression.Match(fieldName); - if (match.Success && match.Groups.Count == 3) + if (match.Success) { - var nonCultureFieldName = match.Groups[1].Value; + var nonCultureFieldName = match.Groups["FieldName"].Value; //check if there's a definition for this and if so return the field definition for the culture field based on the non-culture field if (base.TryGetValue(nonCultureFieldName, out var existingFieldDefinition)) { diff --git a/src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs index b34257ec8c..e2ba56e739 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/EntityMapDefinition.cs @@ -39,7 +39,6 @@ namespace Umbraco.Cms.Core.Models.Mapping target.Trashed = source.Trashed; target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); - if (source is IContentEntitySlim contentSlim) { source.AdditionalData["ContentTypeAlias"] = contentSlim.ContentTypeAlias; @@ -149,6 +148,10 @@ namespace Umbraco.Cms.Core.Models.Mapping if (target.Icon.IsNullOrWhiteSpace()) { + if (source.NodeObjectType == Constants.ObjectTypes.Document) + target.Icon = Constants.Icons.Content; + if (source.NodeObjectType == Constants.ObjectTypes.Media) + target.Icon = Constants.Icons.Content; if (source.NodeObjectType == Constants.ObjectTypes.Member) target.Icon = Constants.Icons.Member; else if (source.NodeObjectType == Constants.ObjectTypes.DataType) @@ -157,6 +160,8 @@ namespace Umbraco.Cms.Core.Models.Mapping target.Icon = Constants.Icons.ContentType; else if (source.NodeObjectType == Constants.ObjectTypes.MediaType) target.Icon = Constants.Icons.MediaType; + else if (source.NodeObjectType == Constants.ObjectTypes.MemberType) + target.Icon = Constants.Icons.MemberType; else if (source.NodeObjectType == Constants.ObjectTypes.TemplateType) target.Icon = Constants.Icons.Template; } @@ -242,7 +247,7 @@ namespace Umbraco.Cms.Core.Models.Mapping switch (entity) { case IMemberEntitySlim memberEntity: - return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member); + return memberEntity.ContentTypeIcon; case IContentEntitySlim contentEntity: // NOTE: this case covers both content and media entities return contentEntity.ContentTypeIcon; diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeDto.cs index 9d000bc771..ca9f5fc1cc 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentTypeDto.cs @@ -9,6 +9,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos internal class ContentTypeDto { public const string TableName = Cms.Core.Constants.DatabaseSchema.Tables.ContentType; + private string _alias; [Column("pk")] [PrimaryKeyColumn(IdentitySeed = 700)] @@ -21,7 +22,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [Column("alias")] [NullSetting(NullSetting = NullSettings.Null)] - public string Alias { get; set; } + public string Alias { get => _alias; set => _alias = value == null ? null : string.Intern(value); } [Column("icon")] [Index(IndexTypes.NonClustered)] diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs index e1505ba05e..340134dde2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyTypeDto.cs @@ -10,6 +10,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [ExplicitColumns] internal class PropertyTypeDto { + private string _alias; + [Column("id")] [PrimaryKeyColumn(IdentitySeed = 100)] public int Id { get; set; } @@ -29,7 +31,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")] [Column("Alias")] - public string Alias { get; set; } + public string Alias { get => _alias; set => _alias = value == null ? null : string.Intern(value); } [Column("Name")] [NullSetting(NullSetting = NullSettings.Null)] diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index 3c289aa542..1977e0fce1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -133,7 +134,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (objectTypes.Any()) { - sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", new { objectTypes = objectTypes }); + sql = sql.WhereIn(dto => dto.NodeObjectType, objectTypes); } return Database.Fetch(sql); @@ -172,7 +173,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoNode.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Node}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs index 17f0a8101a..0ec31d843f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -65,10 +66,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false).Where("cmsDictionary.pk > 0"); + var sql = GetBaseQuery(false).Where(x => x.PrimaryKey > 0); if (ids.Any()) { - sql.Where("cmsDictionary.pk in (@ids)", new { /*ids =*/ ids }); + sql.WhereIn(x => x.PrimaryKey, ids); } return Database @@ -112,7 +113,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "cmsDictionary.pk = @id"; + return $"{Constants.DatabaseSchema.Tables.DictionaryEntry}.pk = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs index 4db9a3b0cc..d7fd2e9c7c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs @@ -4,6 +4,7 @@ using System.Data; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -35,10 +36,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false).Where("umbracoDomain.id > 0"); + var sql = GetBaseQuery(false).Where(x => x.Id > 0); if (ids.Any()) { - sql.Where("umbracoDomain.id in (@ids)", new { ids = ids }); + sql.WhereIn(x => x.Id, ids); } return Database.Fetch(sql).Select(ConvertFromDto); @@ -69,7 +70,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoDomain.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Domain}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 2eec8b661b..64200d3bbe 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence; @@ -43,7 +44,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var toInsert = new List(logins); var existingLogins = Database.Fetch(sql); - + foreach (var existing in existingLogins) { var found = logins.FirstOrDefault(x => @@ -151,7 +152,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return sql; } - protected override string GetBaseWhereClause() => "umbracoExternalLogin.id = @id"; + protected override string GetBaseWhereClause() => $"{Constants.DatabaseSchema.Tables.ExternalLogin}.id = @id"; protected override IEnumerable GetDeleteClauses() { @@ -237,7 +238,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement var toInsert = new List(tokens); var existingTokens = Database.Fetch(sql); - + foreach (ExternalLoginTokenDto existing in existingTokens) { IExternalLoginToken found = tokens.FirstOrDefault(x => @@ -275,13 +276,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement throw new InvalidOperationException($"A token was attempted to be saved for login provider {t.LoginProvider} which is not assigned to this user"); } insertDtos.Add(ExternalLoginFactory.BuildDto(externalLoginId, t)); - } + } Database.InsertBulk(insertDtos); } private Sql GetBaseTokenQuery(bool forUpdate) => forUpdate ? Sql() - .Select(r => r.Select(x => x.ExternalLoginDto)) + .Select(r => r.Select(x => x.ExternalLoginDto)) .From() .Append(" WITH (UPDLOCK)") // ensure these table values are locked for updates, the ForUpdate ext method does not work here .InnerJoin() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index 90d53df9ab..d0aee52406 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -4,6 +4,7 @@ using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; @@ -48,15 +49,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override IEnumerable PerformGetAll(params int[] ids) { - var sql = GetBaseQuery(false).Where("umbracoLanguage.id > 0"); + var sql = GetBaseQuery(false).Where(x => x.Id > 0); if (ids.Any()) { - sql.Where("umbracoLanguage.id in (@ids)", new { ids = ids }); + sql.WhereIn(x => x.Id, ids); } //this needs to be sorted since that is the way legacy worked - default language is the first one!! //even though legacy didn't sort, it should be by id - sql.OrderBy(dto => dto.Id); + sql.OrderBy(x => x.Id); // get languages var languages = Database.Fetch(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); @@ -97,14 +98,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement ? sql.SelectCount() : sql.Select(); - sql - .From(); + sql.From(); + return sql; } protected override string GetBaseWhereClause() { - return "umbracoLanguage.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Language}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 0a9eb7b0ef..535895e8ed 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -36,7 +37,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public IMacro Get(Guid id) { - var sql = GetBaseQuery().Where("uniqueId=@Id", new { Id = id }); + var sql = GetBaseQuery().Where(x => x.UniqueId == id); return GetBySql(sql); } @@ -123,7 +124,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "cmsMacro.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Macro}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs index 48659829ee..26d24cbac0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -88,7 +89,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoNode.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Node}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs index 5ad27b7506..6563bbde97 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -43,7 +43,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .Where(dto => dto.NodeObjectType == NodeObjectTypeId); if (ids.Any()) - sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); + sql.WhereIn(x => x.NodeId, ids); return Database.Fetch(sql).Select(x => MemberGroupFactory.BuildEntity(x)); } @@ -123,7 +123,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public IMemberGroup Get(Guid uniqueId) { var sql = GetBaseQuery(false); - sql.Where("umbracoNode.uniqueId = @uniqueId", new { uniqueId }); + sql.Where(x => x.UniqueId == uniqueId); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); @@ -260,7 +260,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement .InnerJoin() .On(dto => dto.NodeId, dto => dto.MemberGroup) .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where("cmsMember2MemberGroup.Member in (@ids)", new { ids = memberIds }); + .WhereIn(x => x.Member, memberIds); currentlyAssigned = Database.Fetch(assignedSql).ToArray(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index cc1ce7aec0..22083eae30 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -536,9 +536,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement foreach (var batch in inGroups) { var memberIdBatch = batch.Select(x => x.Id); + var sql = Sql().SelectAll().From() .Where(dto => dto.MemberGroup == memberGroup.Id) - .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); + .WhereIn(dto => dto.Member, memberIdBatch); + var memberIdsInGroup = Database.Fetch(sql) .Select(x => x.Member).ToArray(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs index 342b1e025c..32a4d71cb8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -119,7 +119,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoNode.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Node}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs index a618e0e49f..1c4dcdb675 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; @@ -38,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement if (ids.Any()) { - sql.Where("umbracoAccess.id IN (@ids)", new { ids }); + sql.WhereIn(x => x.Id, ids); } sql.OrderBy(x => x.NodeId); @@ -68,7 +69,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoAccess.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Access}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index f531c61037..749fc9d77b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -114,7 +114,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoRelation.id = @id"; + return $"{Constants.DatabaseSchema.Tables.Relation}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs index f6f3454560..151d08a2ff 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -108,7 +109,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoRelationType.id = @id"; + return $"{Constants.DatabaseSchema.Tables.RelationType}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 4e4f52c93a..2039c7f2eb 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -123,7 +124,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return sql; } - protected override string GetBaseWhereClause() => Cms.Core.Constants.DatabaseSchema.Tables.Node + ".id = @id"; + protected override string GetBaseWhereClause() => $"{Constants.DatabaseSchema.Tables.Node}.id = @id"; protected override IEnumerable GetDeleteClauses() { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index ad5e36c0ab..a8c6334416 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -276,7 +277,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override string GetBaseWhereClause() { - return "umbracoUserGroup.id = @id"; + return $"{Constants.DatabaseSchema.Tables.UserGroup}.id = @id"; } protected override IEnumerable GetDeleteClauses() diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 71bc5b8d33..d83be68732 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -425,7 +425,7 @@ ORDER BY colName"; protected override string GetBaseWhereClause() { - return "umbracoUser.id = @id"; + return $"{Constants.DatabaseSchema.Tables.User}.id = @id"; } protected override IEnumerable GetDeleteClauses() @@ -574,7 +574,7 @@ ORDER BY colName"; { userDto.EmailConfirmedDate = null; changedCols.Add("emailConfirmedDate"); - + // If the security stamp hasn't already updated we need to force it if (entity.IsPropertyDirty("SecurityStamp") == false) { diff --git a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs index e37a31f1f6..14627db05c 100644 --- a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Core.PublishedCache /// Represents a content type cache. /// /// This cache is not snapshotted, so it refreshes any time things change. - public class PublishedContentTypeCache + public class PublishedContentTypeCache : IDisposable { // NOTE: These are not concurrent dictionaries because all access is done within a lock private readonly Dictionary _typesByAlias = new Dictionary(); @@ -318,6 +318,8 @@ namespace Umbraco.Cms.Core.PublishedCache // for unit tests - changing the callback must reset the cache obviously // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id private Func _getPublishedContentTypeById; + private bool _disposedValue; + internal Func GetPublishedContentTypeById { get => _getPublishedContentTypeById; @@ -365,5 +367,24 @@ namespace Umbraco.Cms.Core.PublishedCache { return GetAliasKey(contentType.ItemType, contentType.Alias); } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _lock.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index f423347b0a..c50d4f3f45 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -54,11 +54,14 @@ namespace Umbraco.Cms.Core.Routing _logger.LogDebug("Looking for a page to handle 404."); + int? domainContentId = null; + // try to find a culture as best as we can string errorCulture = CultureInfo.CurrentUICulture.Name; if (frequest.Domain != null) { errorCulture = frequest.Domain.Culture; + domainContentId = frequest.Domain.ContentId; } else { @@ -90,8 +93,9 @@ namespace Umbraco.Cms.Core.Routing var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( _contentSettings.Error404Collection.ToArray(), _entityService, - new PublishedContentQuery(umbracoContext.PublishedSnapshot, umbracoContext.VariationContextAccessor, _examineManager), - errorCulture); + new PublishedContentQuery(umbCtx.PublishedSnapshot, umbCtx.VariationContextAccessor, _examineManager), + errorCulture, + domainContentId); IPublishedContent content = null; diff --git a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs index 85a27af0e8..a94f73116a 100644 --- a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs @@ -20,7 +20,8 @@ namespace Umbraco.Cms.Core.Routing ContentErrorPage[] error404Collection, IEntityService entityService, IPublishedContentQuery publishedContentQuery, - string errorCulture) + string errorCulture, + int? domainContentId) { if (error404Collection.Length > 1) { @@ -30,12 +31,12 @@ namespace Umbraco.Cms.Core.Routing if (cultureErr != null) { - return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); + return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery, domainContentId); } } else if (error404Collection.Length == 1) { - return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery); + return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery, domainContentId); } return null; @@ -44,7 +45,11 @@ namespace Umbraco.Cms.Core.Routing /// /// Returns the content id based on the configured ContentErrorPage section. /// - internal static int? GetContentIdFromErrorPageConfig(ContentErrorPage errorPage, IEntityService entityService, IPublishedContentQuery publishedContentQuery) + internal static int? GetContentIdFromErrorPageConfig( + ContentErrorPage errorPage, + IEntityService entityService, + IPublishedContentQuery publishedContentQuery, + int? domainContentId) { if (errorPage.HasContentId) { @@ -73,7 +78,7 @@ namespace Umbraco.Cms.Core.Routing // we have an xpath statement to execute var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery( xpathExpression: errorPage.ContentXPath, - nodeContextId: null, + nodeContextId: domainContentId, getPath: nodeid => { IEntitySlim ent = entityService.Get(nodeid); diff --git a/src/Umbraco.Infrastructure/Services/IdKeyMap.cs b/src/Umbraco.Infrastructure/Services/IdKeyMap.cs index 3850e607b3..49747534c6 100644 --- a/src/Umbraco.Infrastructure/Services/IdKeyMap.cs +++ b/src/Umbraco.Infrastructure/Services/IdKeyMap.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Scoping; namespace Umbraco.Cms.Core.Services { - public class IdKeyMap : IIdKeyMap + public class IdKeyMap : IIdKeyMap,IDisposable { private readonly IScopeProvider _scopeProvider; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); @@ -46,6 +46,7 @@ namespace Umbraco.Cms.Core.Services private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary = new ConcurrentDictionary id2key, Func key2id)>(); + private bool _disposedValue; public void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id) { @@ -106,8 +107,8 @@ namespace Umbraco.Cms.Core.Services } finally { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); } } @@ -125,8 +126,8 @@ namespace Umbraco.Cms.Core.Services } finally { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); } } #endif @@ -369,5 +370,23 @@ namespace Umbraco.Cms.Core.Services public T Id { get; } } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs index e4633714ff..99f21b4fc3 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs @@ -573,7 +573,7 @@ namespace Umbraco.Cms.Core.Services.Implement query.Where(member => member.Username.EndsWith(login)); break; case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + query.Where(member => member.Username.SqlWildcard(login, TextColumnType.NVarchar)); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index 0500c18bdd..b9fceeffec 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -823,8 +823,9 @@ namespace Umbraco.Cms.Core.Services.Implement var groupUsers = userGroup.HasIdentity ? _userRepository.GetAllInGroup(userGroup.Id).ToArray() : empty; var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x); var groupIds = groupUsers.Select(x => x.Id).ToArray(); + var addedUserIds = userIds.Except(groupIds); - addedUsers = _userRepository.GetMany(userIds.Except(groupIds).ToArray()).Where(x => x.Id != 0).ToArray(); + addedUsers = addedUserIds.Count() > 0 ? _userRepository.GetMany(addedUserIds.ToArray()).Where(x => x.Id != 0).ToArray() : new IUser[] { }; removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray(); } diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index 2f709d5d27..88a43fd1c1 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -46,7 +46,6 @@ - diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs index 2603937fc5..401656e37e 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs @@ -11,6 +11,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource /// internal class DictionaryOfPropertyDataSerializer : SerializerBase, ISerializer>, IDictionaryOfPropertyDataSerializer { + private static readonly PropertyData[] Empty = Array.Empty(); public IDictionary ReadFrom(Stream stream) { @@ -26,6 +27,11 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource // read values count var vcount = PrimitiveSerializer.Int32.ReadFrom(stream); + if(vcount == 0) + { + dict[key] = Empty; + continue; + } // create pdata and add to the dictionary var pdatas = new PropertyData[vcount]; diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs index c813e428d2..2d276fbaf4 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs @@ -1,7 +1,7 @@ -using System.Configuration; using CSharpTest.Net.Collections; using CSharpTest.Net.Serialization; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Exceptions; namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource { @@ -54,9 +54,9 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource for (var i = blockSize; i != 1; i >>= 1) bit++; if (1 << bit != blockSize) - throw new ConfigurationErrorsException($"Invalid block size value \"{blockSize}\": must be a power of two."); + throw new ConfigurationException($"Invalid block size value \"{blockSize}\": must be a power of two."); if (blockSize < 512 || blockSize > 65536) - throw new ConfigurationErrorsException($"Invalid block size value \"{blockSize}\": must be >= 512 and <= 65536."); + throw new ConfigurationException($"Invalid block size value \"{blockSize}\": must be >= 512 and <= 65536."); return blockSize; } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializer.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializer.cs index 7caf6eac7d..ba45dec9a8 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializer.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializer.cs @@ -101,6 +101,10 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache.DataSource { property.Value = LZ4Pickler.Pickle(Encoding.UTF8.GetBytes((string)property.Value), LZ4Level.L00_FAST); } + foreach (var property in propertyAliasToData.Value.Where(x => x.Value != null && x.Value is int intVal)) + { + property.Value = Convert.ToBoolean((int)property.Value); + } } } } diff --git a/src/Umbraco.PublishedCache.NuCache/MemberCache.cs b/src/Umbraco.PublishedCache.NuCache/MemberCache.cs index 2eca1515f6..ade291eacf 100644 --- a/src/Umbraco.PublishedCache.NuCache/MemberCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/MemberCache.cs @@ -1,17 +1,18 @@ +using System; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; namespace Umbraco.Cms.Infrastructure.PublishedCache { - public class MemberCache : IPublishedMemberCache + public class MemberCache : IPublishedMemberCache, IDisposable { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IVariationContextAccessor _variationContextAccessor; private readonly IPublishedModelFactory _publishedModelFactory; private readonly PublishedContentTypeCache _contentTypeCache; private readonly bool _previewDefault; - + private bool _disposedValue; public MemberCache( bool previewDefault, PublishedContentTypeCache contentTypeCache, @@ -36,5 +37,28 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache => PublishedMember.Create(member, GetContentType(member.ContentTypeId), _previewDefault, _publishedSnapshotAccessor, _variationContextAccessor, _publishedModelFactory); #endregion + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _contentTypeCache.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } + + #endregion } } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshot.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshot.cs index f2d54759c5..68446b1dcc 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshot.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshot.cs @@ -35,6 +35,7 @@ namespace Umbraco.Cms.Infrastructure.PublishedCache { ContentCache.Dispose(); MediaCache.Dispose(); + MemberCache.Dispose(); } } diff --git a/src/Umbraco.TestData/LoadTestController.cs b/src/Umbraco.TestData/LoadTestController.cs index 3033d2febb..c245aae816 100644 --- a/src/Umbraco.TestData/LoadTestController.cs +++ b/src/Umbraco.TestData/LoadTestController.cs @@ -44,7 +44,7 @@ namespace Umbraco.TestData

LoadTest

-
" + System.Configuration.ConfigurationManager.AppSettings["umbracoConfigurationStatus"] + @"
+
@_umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild()
"; @@ -53,13 +53,14 @@ namespace Umbraco.TestData private static readonly string s_containerTemplateText = @" @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject Umbraco.Cms.Core.Configuration.IUmbracoVersion _umbracoVersion @{ Layout = null; var container = Umbraco.ContentAtRoot().OfTypes(""" + ContainerAlias + @""").FirstOrDefault(); var contents = container.Children().ToArray(); var groups = contents.GroupBy(x => x.Value(""origin"")); var id = contents.Length > 0 ? contents[0].Id : -1; - var wurl = Request.QueryString[""u""] == ""1""; + var wurl = Context.Request.Query[""u""] == ""1""; var missing = contents.Length > 0 && contents[contents.Length - 1].Id - contents[0].Id >= contents.Length; } " + s_headHtml + @" @@ -81,7 +82,7 @@ namespace Umbraco.TestData } if (wurl) { -
@content.Id :: @content.Name :: @content.Url
+
@content.Id :: @content.Name :: @content.Url()
} else { @@ -178,7 +179,7 @@ namespace Umbraco.TestData } } - private IActionResult ContentHtml(string s) => Content(s_headHtml + s + FootHtml); + private IActionResult ContentHtml(string s) => Content(s_headHtml + s + FootHtml, "text/html"); public IActionResult Install() { diff --git a/src/Umbraco.TestData/SegmentTestController.cs b/src/Umbraco.TestData/SegmentTestController.cs index 8eabd65288..92c01bcf7f 100644 --- a/src/Umbraco.TestData/SegmentTestController.cs +++ b/src/Umbraco.TestData/SegmentTestController.cs @@ -1,7 +1,7 @@ using System; -using System.Configuration; using System.Linq; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; @@ -11,25 +11,30 @@ using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Web.Website.Controllers; using Umbraco.Extensions; +using Umbraco.TestData.Configuration; namespace Umbraco.TestData { public class SegmentTestController : SurfaceController { + private IOptions _testDataSettings; + public SegmentTestController( IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IOptions testDataSettings) : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) { + _testDataSettings = testDataSettings; } public IActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias) { - if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + if(_testDataSettings.Value.Enabled != true) { return HttpNotFound(); } @@ -62,7 +67,7 @@ namespace Umbraco.TestData public IActionResult DisableDocTypeSegments(string alias) { - if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + if (_testDataSettings.Value.Enabled != true) { return HttpNotFound(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs index c55b577845..cbdb2ac5b8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs @@ -33,7 +33,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing typeof(System.Guid).Assembly, typeof(NUnit.Framework.Assert).Assembly, typeof(System.Xml.NameTable).Assembly, - typeof(System.Configuration.GenericEnumConverter).Assembly, typeof(TypeFinder).Assembly, }; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs index cea18c5176..c71d89856e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs @@ -40,7 +40,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing typeof(Guid).Assembly, typeof(Assert).Assembly, typeof(System.Xml.NameTable).Assembly, - typeof(System.Configuration.GenericEnumConverter).Assembly, ////typeof(TabPage).Assembly, typeof(TypeFinder).Assembly, typeof(UmbracoContext).Assembly, diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/PublicAccessCheckerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/PublicAccessCheckerTests.cs index 52c68b551f..c2760e92f3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/PublicAccessCheckerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/PublicAccessCheckerTests.cs @@ -5,6 +5,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Text; using System.Threading.Tasks; +using AutoFixture.NUnit3; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -117,15 +118,28 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Security [AutoMoqData] [Test] - public async Task GivenMemberLoggedIn_WhenMemberHasNoRoles_ThenAccessDeniedResult( + public async Task GivenMemberLoggedIn_WhenMemberHasNoRolesAndWrongUsername_ThenAccessDeniedResult( IMemberManager memberManager, IPublicAccessService publicAccessService, - IContentService contentService) + IContentService contentService, + IContent protectedNode, + IContent loginNode, + IContent noAccessNode, + string username) { PublicAccessChecker sut = CreateSut(memberManager, publicAccessService, contentService, out HttpContext httpContext); + Mock.Get(publicAccessService).Setup(x => x.GetEntryForContent(It.IsAny())) + .Returns(new PublicAccessEntry(protectedNode, loginNode, noAccessNode, new [] + { + new PublicAccessRule(Guid.Empty, Guid.Empty) + { + RuleType = Constants.Conventions.PublicAccess.MemberUsernameRuleType, + RuleValue = "AnotherUsername" + } + })); httpContext.User = GetLoggedInUser(); - MockGetUserAsync(memberManager, new MemberIdentityUser()); + MockGetUserAsync(memberManager, new MemberIdentityUser(){IsApproved = true, UserName = username}); MockGetRolesAsync(memberManager, Enumerable.Empty()); var result = await sut.HasMemberAccessToContentAsync(123); diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 4a20c1c1bf..3bb668535d 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -75,7 +75,7 @@ - + @@ -101,10 +101,18 @@ - - - - + + + + + + + + + + + + diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 6cb0addb71..183284d5b8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -913,9 +913,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers v.Notifications.AddRange(n.Notifications); } + //lastly, if it is not valid, add the model state to the outgoing object and throw a 400 HandleInvalidModelState(display, cultureForInvariantErrors); - //lastly, if it is not valid, add the model state to the outgoing object and throw a 400 if (!ModelState.IsValid) { return ValidationProblem(display, ModelState); diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 3169c9f64b..afe154ca48 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -503,7 +503,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } //all udi types will need to be the same in this list so we'll determine by the first - //currently we only support GuidIdi for this method + //currently we only support GuidUdi for this method var guidUdi = ids[0] as GuidUdi; if (guidUdi != null) @@ -1183,16 +1183,17 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } private static readonly string[] _postFilterSplitStrings = new[] - { - "=", - "==", - "!=", - "<>", - ">", - "<", - ">=", - "<=" - }; + { + "=", + "==", + "!=", + "<>", + ">", + "<", + ">=", + "<=" + }; + private static QueryCondition BuildQueryCondition(string postFilter) { var postFilterParts = postFilter.Split(_postFilterSplitStrings, 2, StringSplitOptions.RemoveEmptyEntries); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 6cada09db3..d8ee3126a4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -368,7 +368,45 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers if (created.Succeeded == false) { - return ValidationProblem(created.Errors.ToErrorMessage()); + MemberDisplay forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); + foreach (IdentityError error in created.Errors) + { + switch (error.Code) + { + case nameof(IdentityErrorDescriber.InvalidUserName): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.PasswordMismatch): + case nameof(IdentityErrorDescriber.PasswordRequiresDigit): + case nameof(IdentityErrorDescriber.PasswordRequiresLower): + case nameof(IdentityErrorDescriber.PasswordRequiresNonAlphanumeric): + case nameof(IdentityErrorDescriber.PasswordRequiresUniqueChars): + case nameof(IdentityErrorDescriber.PasswordRequiresUpper): + case nameof(IdentityErrorDescriber.PasswordTooShort): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.InvalidEmail): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.DuplicateUserName): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case nameof(IdentityErrorDescriber.DuplicateEmail): + ModelState.AddPropertyError( + new ValidationResult(error.Description, new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + } + } + return ValidationProblem(forDisplay, ModelState); } // now re-look up the member, which will now exist diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index 9d95dee395..d63f6b0eda 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -143,7 +143,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers /// A containing the persisted relation type's ID. public ActionResult PostCreate(RelationTypeSave relationType) { - var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(_shortStringHelper, true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType); + var relationTypePersisted = new RelationType( + relationType.Name, + relationType.Name.ToSafeAlias(_shortStringHelper, true), + relationType.IsBidirectional, + relationType.ParentObjectType, + relationType.ChildObjectType); try { diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs index d4aafe83ff..4b8fb6acb3 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs @@ -68,6 +68,12 @@ namespace Umbraco.Cms.Web.BackOffice.Filters var model = (ContentItemSave) context.ActionArguments["contentItem"]; var contentItemValidator = new ContentSaveModelValidator(_loggerFactory.CreateLogger(), _propertyValidationService); + if (context.ModelState.ContainsKey("contentItem")) + { + // if the entire model is marked as error, remove it, we handle everything separately + context.ModelState.Remove("contentItem"); + } + if (!ValidateAtLeastOneVariantIsBeingSaved(model, context)) return; if (!contentItemValidator.ValidateExistingContent(model, context)) return; if (!await ValidateUserAccessAsync(model, context)) return; diff --git a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs index fc1457798a..a5813a2e8e 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs @@ -89,6 +89,7 @@ namespace Umbraco.Cms.Web.BackOffice.Mapping // Umbraco.Code.MapAll -Icon -Trashed -ParentId -Alias private void Map(IMemberGroup source, MemberGroupDisplay target, MapperContext context) { + target.Icon = Constants.Icons.MemberGroup; target.Id = source.Id; target.Key = source.Key; target.Name = source.Name; diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index 4ab50ecdc1..0b9801f871 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; @@ -22,6 +23,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore private readonly IWebHostEnvironment _webHostEnvironment; private string _applicationId; private string _localTempPath; + private UrlMode _urlProviderMode; public AspNetCoreHostingEnvironment( IServiceProvider serviceProvider, @@ -33,6 +35,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore _hostingSettings = hostingSettings ?? throw new ArgumentNullException(nameof(hostingSettings)); _webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings)); _webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment)); + _urlProviderMode = _webRoutingSettings.CurrentValue.UrlProviderMode; SiteName = webHostEnvironment.ApplicationName; ApplicationPhysicalPath = webHostEnvironment.ContentRootPath; @@ -144,7 +147,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore /// public string ToAbsolute(string virtualPath) { - if (!virtualPath.StartsWith("~/") && !virtualPath.StartsWith("/")) + if (!virtualPath.StartsWith("~/") && !virtualPath.StartsWith("/") && _urlProviderMode != UrlMode.Absolute) { throw new InvalidOperationException($"The value {virtualPath} for parameter {nameof(virtualPath)} must start with ~/ or /"); } diff --git a/src/Umbraco.Web.Common/Extensions/CacheHelperExtensions.cs b/src/Umbraco.Web.Common/Extensions/CacheHelperExtensions.cs index d52d140640..055e15ef73 100644 --- a/src/Umbraco.Web.Common/Extensions/CacheHelperExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/CacheHelperExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Web; namespace Umbraco.Extensions { @@ -17,34 +18,40 @@ namespace Umbraco.Extensions ///
/// /// + /// /// /// /// - /// + /// /// used to cache the partial view, this key could change if it is cached by page or by member /// /// public static IHtmlContent CachedPartialView( this AppCaches appCaches, IHostingEnvironment hostingEnvironment, + IUmbracoContext umbracoContext, IHtmlHelper htmlHelper, string partialViewName, object model, - int cachedSeconds, + TimeSpan cacheTimeout, string cacheKey, - ViewDataDictionary viewData = null) + ViewDataDictionary viewData = null + ) { //disable cached partials in debug mode: http://issues.umbraco.org/issue/U4-5940 - if (hostingEnvironment.IsDebugMode) + //disable cached partials in preview mode: https://github.com/umbraco/Umbraco-CMS/issues/10384 + if (hostingEnvironment.IsDebugMode || (umbracoContext?.InPreviewMode == true)) { // just return a normal partial view instead return htmlHelper.Partial(partialViewName, model, viewData); } - return appCaches.RuntimeCache.GetCacheItem( + var result = appCaches.RuntimeCache.GetCacheItem( CoreCacheHelperExtensions.PartialViewCacheKey + cacheKey, - () => htmlHelper.Partial(partialViewName, model, viewData), - timeout: new TimeSpan(0, 0, 0, cachedSeconds)); + () => new HtmlString(htmlHelper.Partial(partialViewName, model, viewData).ToHtmlString()), + timeout: cacheTimeout); + + return result; } } diff --git a/src/Umbraco.Web.Website/Extensions/HtmlContentExtensions.cs b/src/Umbraco.Web.Common/Extensions/HtmlContentExtensions.cs similarity index 100% rename from src/Umbraco.Web.Website/Extensions/HtmlContentExtensions.cs rename to src/Umbraco.Web.Common/Extensions/HtmlContentExtensions.cs diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs index bf455aa000..51e9dae975 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/InMemoryModelFactory.cs @@ -26,7 +26,7 @@ using File = System.IO.File; namespace Umbraco.Cms.Web.Common.ModelsBuilder { - internal class InMemoryModelFactory : IAutoPublishedModelFactory, IRegisteredObject + internal class InMemoryModelFactory : IAutoPublishedModelFactory, IRegisteredObject, IDisposable { private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); @@ -52,7 +52,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder private static readonly Regex s_usingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex s_aattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline); private readonly Lazy _pureLiveDirectory; - + private bool _disposedValue; public InMemoryModelFactory( @@ -788,12 +788,32 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder public void Stop(bool immediate) { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); - _locker.Dispose(); + Dispose(); + _hostingLifetime.UnregisterObject(this); } + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _watcher.EnableRaisingEvents = false; + _watcher.Dispose(); + _locker.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } + internal class Infos { public Dictionary ModelTypeMap { get; set; } diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index 27b8638aa1..505abdbe0e 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -143,7 +143,7 @@ namespace Umbraco.Cms.Web.Common.Security IdentityResult result = await base.SetLockoutEndDateAsync(user, lockoutEnd); // The way we unlock is by setting the lockoutEnd date to the current datetime - if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) + if (result.Succeeded && lockoutEnd > DateTimeOffset.UtcNow) { NotifyAccountLocked(_httpContextAccessor.HttpContext?.User, user.Id); } diff --git a/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs b/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs index 8e6174e5f1..ad9b39a7bb 100644 --- a/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs +++ b/src/Umbraco.Web.Common/Security/PublicAccessChecker.cs @@ -39,11 +39,6 @@ namespace Umbraco.Cms.Web.Common.Security var username = currentMember.UserName; IList userRoles = await memberManager.GetRolesAsync(currentMember); - if (userRoles.Count == 0) - { - return PublicAccessStatus.AccessDenied; - } - if (!currentMember.IsApproved) { return PublicAccessStatus.NotApproved; diff --git a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs index 80729412a3..4817956ef8 100644 --- a/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs +++ b/src/Umbraco.Web.Common/Security/UmbracoSignInManager.cs @@ -58,11 +58,17 @@ namespace Umbraco.Cms.Web.Common.Security var auth = await Context.AuthenticateAsync(ExternalAuthenticationType); var items = auth?.Properties?.Items; - if (auth?.Principal == null || items == null || !items.ContainsKey(UmbracoSignInMgrLoginProviderKey)) + if (auth?.Principal == null || items == null) { + Logger.LogDebug("The external login authentication failed. No user Principal or authentication items was resolved."); return null; } + if (!items.ContainsKey(UmbracoSignInMgrLoginProviderKey)) + { + throw new InvalidOperationException($"The external login authenticated successfully but the key {UmbracoSignInMgrLoginProviderKey} was not found in the authentication properties. Ensure you call SignInManager.ConfigureExternalAuthenticationProperties before issuing a ChallengeResult."); + } + if (expectedXsrf != null) { if (!items.ContainsKey(UmbracoSignInMgrXsrfKey)) diff --git a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs index 20b37fe802..0e02c5c4aa 100644 --- a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs @@ -152,7 +152,8 @@ namespace Umbraco.Cms.Web.Common.Templates { var httpContext = _httpContextAccessor.GetRequiredHttpContext(); - var viewResult = _viewEngine.GetView(null, $"~/Views/{request.GetTemplateAlias()}.cshtml", false); + // isMainPage is set to true here to ensure ViewStart(s) found in the view hierarchy are rendered + var viewResult = _viewEngine.GetView(null, $"~/Views/{request.GetTemplateAlias()}.cshtml", isMainPage: true); if (viewResult.Success == false) { diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index fdee5e7785..e8a40e9d70 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -232,10 +232,10 @@ function dependencies() { { "name": "nouislider", "src": [ - "./node_modules/nouislider/distribute/nouislider.min.js", - "./node_modules/nouislider/distribute/nouislider.min.css" + "./node_modules/nouislider/dist/nouislider.min.js", + "./node_modules/nouislider/dist/nouislider.min.css" ], - "base": "./node_modules/nouislider/distribute" + "base": "./node_modules/nouislider/dist" }, { "name": "signalr", diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 6e8a065f1e..3b55d1fc91 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1934,7 +1934,8 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "dev": true, + "optional": true }, "base64id": { "version": "1.0.0", @@ -2143,7 +2144,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "dev": true, + "optional": true }, "got": { "version": "8.3.2", @@ -2221,6 +2223,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -2262,6 +2265,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, + "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -2271,13 +2275,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2293,6 +2299,7 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2433,6 +2440,7 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "optional": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -2458,7 +2466,8 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "optional": true }, "buffer-equal": { "version": "1.0.0", @@ -2655,6 +2664,7 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", "dev": true, + "optional": true, "requires": { "get-proxy": "^2.0.0", "isurl": "^1.0.0-alpha5", @@ -3184,6 +3194,7 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "dev": true, + "optional": true, "requires": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -3239,6 +3250,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", "dev": true, + "optional": true, "requires": { "safe-buffer": "5.1.2" } @@ -3680,6 +3692,7 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -3696,6 +3709,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, + "optional": true, "requires": { "pify": "^3.0.0" }, @@ -3704,7 +3718,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } } @@ -3715,6 +3730,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, + "optional": true, "requires": { "mimic-response": "^1.0.0" } @@ -3724,6 +3740,7 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "dev": true, + "optional": true, "requires": { "file-type": "^5.2.0", "is-stream": "^1.1.0", @@ -3734,7 +3751,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3743,6 +3761,7 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", @@ -3755,7 +3774,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true + "dev": true, + "optional": true } } }, @@ -3764,6 +3784,7 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "dev": true, + "optional": true, "requires": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", @@ -3774,7 +3795,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -3783,6 +3805,7 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", "dev": true, + "optional": true, "requires": { "file-type": "^3.8.0", "get-stream": "^2.2.0", @@ -3794,13 +3817,15 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "dev": true, + "optional": true, "requires": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" @@ -3810,7 +3835,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4098,7 +4124,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4115,7 +4142,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true + "dev": true, + "optional": true }, "duplexify": { "version": "3.7.1", @@ -4766,6 +4794,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, + "optional": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -4907,6 +4936,7 @@ "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, + "optional": true, "requires": { "mime-db": "^1.28.0" } @@ -4916,6 +4946,7 @@ "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, + "optional": true, "requires": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -5153,6 +5184,7 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "optional": true, "requires": { "pend": "~1.2.0" } @@ -5191,13 +5223,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true + "dev": true, + "optional": true }, "filenamify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", "dev": true, + "optional": true, "requires": { "filename-reserved-regex": "^2.0.0", "strip-outer": "^1.0.0", @@ -5374,9 +5408,9 @@ } }, "flatpickr": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", - "integrity": "sha1-R8itRyoJbl+350uAmwcDU1OD8g0=" + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz", + "integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==" }, "flatted": { "version": "2.0.1", @@ -5546,7 +5580,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "dev": true, + "optional": true }, "fs-mkdirp-stream": { "version": "1.0.0", @@ -5593,7 +5628,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5614,12 +5650,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5634,17 +5672,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5761,7 +5802,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5773,6 +5815,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5787,6 +5830,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5794,12 +5838,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5818,6 +5864,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5898,7 +5945,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5910,6 +5958,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5995,7 +6044,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6031,6 +6081,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6050,6 +6101,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6093,12 +6145,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -6125,6 +6179,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", "dev": true, + "optional": true, "requires": { "npm-conf": "^1.1.0" } @@ -6133,13 +6188,15 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "dev": true, + "optional": true }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, + "optional": true, "requires": { "pump": "^3.0.0" }, @@ -6149,6 +6206,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -6261,7 +6319,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "optional": true }, "pump": { "version": "3.0.0", @@ -7464,7 +7523,8 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true + "dev": true, + "optional": true }, "has-symbols": { "version": "1.0.0", @@ -7477,6 +7537,7 @@ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, + "optional": true, "requires": { "has-symbol-support-x": "^1.4.1" } @@ -7682,7 +7743,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "dev": true, + "optional": true }, "ignore": { "version": "4.0.6", @@ -7822,7 +7884,8 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "dev": true, + "optional": true }, "svgo": { "version": "1.3.2", @@ -7894,6 +7957,7 @@ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, + "optional": true, "requires": { "repeating": "^2.0.0" } @@ -8220,7 +8284,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true + "dev": true, + "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -8270,7 +8335,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true + "dev": true, + "optional": true }, "is-negated-glob": { "version": "1.0.0", @@ -8308,13 +8374,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true + "dev": true, + "optional": true }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "dev": true, + "optional": true }, "is-plain-object": { "version": "2.0.4", @@ -8384,13 +8452,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true + "dev": true, + "optional": true }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "dev": true, + "optional": true }, "is-svg": { "version": "3.0.0", @@ -8485,6 +8555,7 @@ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, + "optional": true, "requires": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -9380,7 +9451,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "dev": true, + "optional": true }, "lpad-align": { "version": "1.1.2", @@ -9450,7 +9522,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", @@ -9618,7 +9691,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "dev": true, + "optional": true }, "minimatch": { "version": "3.0.4", @@ -9853,9 +9927,9 @@ "dev": true }, "nouislider": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.6.4.tgz", - "integrity": "sha512-PVCGYl+aC7/nVEbW61ypJWfuW3UCpvctz/luxpt4byxxli1FFyjBX9NIiy4Yak9AaO6a5BkPGfFYMCW4eg3eeQ==" + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-15.2.0.tgz", + "integrity": "sha512-LoZwN+wwU+z/0/UtjQY8VUcVElFJX6GfHkgJ5z1NIt/+KpOsjxEW4EAtBkBWvHNxf9HBzR62K6ft9NpNBkA5/w==" }, "now-and-later": { "version": "2.0.1", @@ -12965,6 +13039,7 @@ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", "dev": true, + "optional": true, "requires": { "config-chain": "^1.1.11", "pify": "^3.0.0" @@ -12974,7 +13049,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "dev": true, + "optional": true } } }, @@ -12983,6 +13059,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, + "optional": true, "requires": { "path-key": "^2.0.0" } @@ -13351,7 +13428,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "dev": true, + "optional": true }, "p-is-promise": { "version": "1.1.0", @@ -13388,6 +13466,7 @@ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", "dev": true, + "optional": true, "requires": { "p-finally": "^1.0.0" } @@ -13578,7 +13657,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "dev": true, + "optional": true }, "performance-now": { "version": "2.1.0", @@ -14085,7 +14165,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true + "dev": true, + "optional": true }, "prr": { "version": "1.0.1", @@ -14443,6 +14524,7 @@ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, + "optional": true, "requires": { "is-finite": "^1.0.0" } @@ -14797,6 +14879,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", "dev": true, + "optional": true, "requires": { "commander": "^2.8.1" } @@ -15191,6 +15274,7 @@ "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, + "optional": true, "requires": { "is-plain-obj": "^1.0.0" } @@ -15200,6 +15284,7 @@ "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", "dev": true, + "optional": true, "requires": { "sort-keys": "^1.0.0" } @@ -15547,6 +15632,7 @@ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "dev": true, + "optional": true, "requires": { "is-natural-number": "^4.0.1" } @@ -15555,7 +15641,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "dev": true, + "optional": true }, "strip-final-newline": { "version": "2.0.0", @@ -15585,6 +15672,7 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15710,6 +15798,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, + "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15724,13 +15813,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "dev": true, + "optional": true }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, + "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15746,6 +15837,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15756,13 +15848,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true + "dev": true, + "optional": true }, "tempfile": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", "dev": true, + "optional": true, "requires": { "temp-dir": "^1.0.0", "uuid": "^3.0.1" @@ -15876,7 +15970,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true + "dev": true, + "optional": true }, "timers-ext": { "version": "0.1.7", @@ -15933,7 +16028,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true + "dev": true, + "optional": true }, "to-fast-properties": { "version": "2.0.0", @@ -16035,6 +16131,7 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, + "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -16170,6 +16267,7 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "optional": true, "requires": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -16378,7 +16476,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true + "dev": true, + "optional": true }, "use": { "version": "3.1.1", @@ -16872,6 +16971,7 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 813ba75095..0b869b4b06 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -34,7 +34,7 @@ "chart.js": "^2.9.3", "clipboard": "2.0.4", "diff": "3.5.0", - "flatpickr": "4.5.2", + "flatpickr": "4.6.9", "font-awesome": "4.7.0", "jquery": "^3.5.1", "jquery-ui-dist": "1.12.1", @@ -42,7 +42,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "14.6.4", + "nouislider": "15.2.0", "npm": "^6.14.7", "spectrum-colorpicker2": "2.0.8", "tinymce": "4.9.11", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js index dd6fa5c5c8..d1be694fde 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/buttons/umbbutton.directive.js @@ -135,7 +135,7 @@ Use this directive to render an umbraco button. The directive can be used to gen if (vm.buttonStyle) { // make it possible to pass in multiple styles - if(vm.buttonStyle.startsWith("[") && vm.buttonStyle.endsWith("]")) { + if (vm.buttonStyle.startsWith("[") && vm.buttonStyle.endsWith("]")) { // when using an attr it will always be a string so we need to remove square brackets // and turn it into and array @@ -143,16 +143,16 @@ Use this directive to render an umbraco button. The directive can be used to gen // split array by , + make sure to catch whitespaces var array = withoutBrackets.split(/\s?,\s?/g); - angular.forEach(array, function(item){ + Utilities.forEach(array, item => { vm.style = vm.style + " " + "btn-" + item; - if(item === "block") { + if (item === "block") { vm.blockElement = true; } }); } else { vm.style = "btn-" + vm.buttonStyle; - if(vm.buttonStyle === "block") { + if (vm.buttonStyle === "block") { vm.blockElement = true; } } @@ -167,7 +167,7 @@ Use this directive to render an umbraco button. The directive can be used to gen // watch for state changes if (changes.state) { - if(changes.state.currentValue) { + if (changes.state.currentValue) { vm.innerState = changes.state.currentValue; } if (changes.state.currentValue === 'success' || changes.state.currentValue === 'error') { @@ -179,25 +179,25 @@ Use this directive to render an umbraco button. The directive can be used to gen } // watch for disabled changes - if(changes.disabled) { - if(changes.disabled.currentValue) { + if (changes.disabled) { + if (changes.disabled.currentValue) { vm.disabled = changes.disabled.currentValue; } } // watch for label changes - if(changes.label && changes.label.currentValue) { + if (changes.label && changes.label.currentValue) { vm.buttonLabel = changes.label.currentValue; setButtonLabel(); } // watch for label key changes - if(changes.labelKey && changes.labelKey.currentValue) { + if (changes.labelKey && changes.labelKey.currentValue) { setButtonLabel(); } // watch for type changes - if(changes.type) { + if (changes.type) { if (!vm.type) { vm.type = "button";// set the default } @@ -206,23 +206,23 @@ Use this directive to render an umbraco button. The directive can be used to gen } function clickButton(event) { - if(vm.action) { + if (vm.action) { vm.action({$event: event}); } } function setButtonLabel() { // if the button opens a dialog add "..." to the label - if(vm.addEllipsis === "true") { + if (vm.addEllipsis === "true") { vm.buttonLabel = vm.buttonLabel + "..."; } // look up localization key - if(vm.labelKey) { - localizationService.localize(vm.labelKey).then(function(value){ + if (vm.labelKey) { + localizationService.localize(vm.labelKey).then(value => { vm.buttonLabel = value; // if the button opens a dialog add "..." to the label - if(vm.addEllipsis === "true") { + if (vm.addEllipsis === "true") { vm.buttonLabel = vm.buttonLabel + "..."; } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index c20c2a368d..f087f6e084 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -206,11 +206,11 @@ scope.loadingAuditTrail = true; logResource.getPagedEntityLog(scope.auditTrailOptions) - .then(function (data) { + .then(data => { // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { - angular.forEach(data.items, function (item) { + userService.getCurrentUser().then(currentUser => { + Utilities.forEach(data.items, item => { item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL'); }); }); @@ -232,12 +232,12 @@ function loadRedirectUrls() { scope.loadingRedirectUrls = true; //check if Redirect URL Management is enabled - redirectUrlsResource.getEnableState().then(function (response) { + redirectUrlsResource.getEnableState().then(response => { scope.urlTrackerDisabled = response.enabled !== true; if (scope.urlTrackerDisabled === false) { redirectUrlsResource.getRedirectsForContentItem(scope.node.udi) - .then(function (data) { + .then(data => { scope.redirectUrls = data.searchResults; scope.hasRedirects = (typeof data.searchResults !== 'undefined' && data.searchResults.length > 0); scope.loadingRedirectUrls = false; @@ -250,7 +250,7 @@ } function setAuditTrailLogTypeColor(auditTrail) { - angular.forEach(auditTrail, function (item) { + Utilities.forEach(auditTrail, item => { switch (item.logType) { case "Save": @@ -304,7 +304,7 @@ function formatDatesToLocal() { // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { + userService.getCurrentUser().then(currentUser => { scope.currentVariant.createDateFormatted = dateHelper.getLocalDate(scope.currentVariant.createDate, currentUser.locale, 'LLL'); scope.currentVariant.releaseDateFormatted = dateHelper.getLocalDate(scope.currentVariant.releaseDate, currentUser.locale, 'LLL'); scope.currentVariant.expireDateFormatted = dateHelper.getLocalDate(scope.currentVariant.expireDate, currentUser.locale, 'LLL'); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js index eec8455969..a912eab609 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditornavigation.directive.js @@ -17,19 +17,19 @@ name: "More" }; - scope.openNavigationItem = function(item) { + scope.openNavigationItem = item => { scope.showDropdown = false; runItemAction(item); setItemToActive(item); - if(scope.onSelect) { + if (scope.onSelect) { scope.onSelect({"item": item}); } eventsService.emit("app.tabChange", item); }; - scope.openAnchorItem = function(item, anchor) { - if(scope.onAnchorSelect) { + scope.openAnchorItem = (item, anchor) => { + if (scope.onAnchorSelect) { scope.onAnchorSelect({"item": item, "anchor": anchor}); } if (item.active !== true) { @@ -37,11 +37,11 @@ } }; - scope.toggleDropdown = function () { + scope.toggleDropdown = () => { scope.showDropdown = !scope.showDropdown; }; - scope.hideDropdown = function() { + scope.hideDropdown = () => { scope.showDropdown = false; }; @@ -60,7 +60,7 @@ function calculateVisibleItems(windowWidth) { // if we don't get a windowWidth stick with the default item limit - if(!windowWidth) { + if (!windowWidth) { return; } @@ -94,7 +94,7 @@ if (selectedItem.view) { // deselect all items - angular.forEach(scope.navigation, function(item, index){ + Utilities.forEach(scope.navigation, item => { item.active = false; }); @@ -112,8 +112,8 @@ } } - var resizeCallback = function(size) { - if(size && size.width) { + var resizeCallback = size => { + if (size && size.width) { calculateVisibleItems(size.width); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js index 34cb55a794..35981049af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorview.directive.js @@ -59,6 +59,8 @@ Use this directive to construct the main editor window.
  • {@link umbraco.directives.directive:umbEditorContainer umbEditorContainer}
  • {@link umbraco.directives.directive:umbEditorFooter umbEditorFooter}
  • + +@param {boolean} footer Whether the directive should make place for a {@link umbraco.directives.directive:umbEditorFooter umbEditorFooter} at the bottom (`true` by default). **/ (function() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js index 2e9f15913c..5eb22dcecd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbsearchfilter.directive.js @@ -40,6 +40,8 @@ vm.$onInit = onInit; vm.change = change; + vm.keyDown = keyDown; + vm.blur = blur; function onInit() { vm.inputId = vm.inputId || "umb-search-filter_" + String.CreateGuid(); @@ -63,6 +65,23 @@ }, 0); } } + + function blur() { + if (vm.onBlur) { + vm.onBlur(); + } + } + + function keyDown(evt) { + //13: enter + switch (evt.keyCode) { + case 13: + if (vm.onSearch) { + vm.onSearch(); + } + break; + } + } } var component = { @@ -76,6 +95,8 @@ text: "@", labelKey: "@?", onChange: "&?", + onSearch: "&?", + onBlur: "&?", autoFocus: " { // only allow change of media type if user has access to the settings sections - angular.forEach(user.sections, function(section){ - if(section.alias === "settings") { + Utilities.forEach(user.sections, section => { + if (section.alias === "settings") { scope.allowChangeMediaType = true; } }); @@ -52,7 +51,7 @@ function formatDatesToLocal() { // get current backoffice user and format dates - userService.getCurrentUser().then(function (currentUser) { + userService.getCurrentUser().then(currentUser => { scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL'); }); @@ -73,20 +72,20 @@ scope.node.extension = mediaHelper.getFileExtension(scope.nodeUrl); } - scope.openMediaType = function (mediaType) { + scope.openMediaType = mediaType => { var editor = { id: mediaType.id, - submit: function(model) { + submit: model => { editorService.close(); }, - close: function() { + close: () => { editorService.close(); } }; editorService.mediaTypeEditor(editor); }; - scope.openSVG = function () { + scope.openSVG = () => { var popup = window.open('', '_blank'); var html = '' + ''; @@ -136,7 +135,7 @@ function loadMediaRelations() { return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions) - .then(function (data) { + .then(data => { scope.mediaReferences = data; scope.hasMediaReferences = data.items.length > 0; }); @@ -144,7 +143,7 @@ function loadMemberRelations() { return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions) - .then(function (data) { + .then(data => { scope.memberReferences = data; scope.hasMemberReferences = data.items.length > 0; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 7868f79809..358223901e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:umbTree * @restrict E **/ -function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService, backdropService) { +function umbTreeDirective($q, treeService, navigationService, notificationsService) { return { restrict: 'E', @@ -318,18 +318,6 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } - // Close any potential backdrop and remove the #leftcolumn modifier class - function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - - if(isLeftColumnOnTop){ - backdropService.close(); - leftColumn.removeClass(aboveClass); - } - } - /** Returns the css classses assigned to the node (div element) */ $scope.getNodeCssClass = function (node) { if (!node) { @@ -370,7 +358,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use */ $scope.select = function (n, ev) { - closeBackdrop() + navigationService.hideMenu(); if (n.metaData && n.metaData.noAccess === true) { ev.preventDefault(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index ef463e6d95..f36fbc7ea8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js @@ -115,7 +115,7 @@ } // onLoad callbacks - angular.forEach(opts.callbacks, function(cb) { + Utilities.forEach(opts.callbacks, cb => { if (Utilities.isFunction(cb)) { cb(acee); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js index 9a841e3e4a..47441326d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js @@ -126,14 +126,14 @@ Use this directive to render a ui component for selecting child items to a paren scope.dialogModel = {}; scope.showDialog = false; - scope.removeChild = function(selectedChild, $index) { - if(scope.onRemove) { + scope.removeChild = (selectedChild, $index) => { + if (scope.onRemove) { scope.onRemove(selectedChild, $index); } }; - scope.addChild = function($event) { - if(scope.onAdd) { + scope.addChild = $event => { + if (scope.onAdd) { scope.onAdd($event); } }; @@ -141,16 +141,16 @@ Use this directive to render a ui component for selecting child items to a paren function syncParentName() { // update name on available item - angular.forEach(scope.availableChildren, function(availableChild){ - if(availableChild.id === scope.parentId) { - availableChild.name = scope.parentName; + Utilities.forEach(scope.availableChildren, availableChild => { + if (availableChild.id === scope.parentId) { + availableChild.name = scope.parentName; } }); // update name on selected child - angular.forEach(scope.selectedChildren, function(selectedChild){ - if(selectedChild.id === scope.parentId) { - selectedChild.name = scope.parentName; + Utilities.forEach(scope.selectedChildren, selectedChild => { + if (selectedChild.id === scope.parentId) { + selectedChild.name = scope.parentName; } }); @@ -159,16 +159,16 @@ Use this directive to render a ui component for selecting child items to a paren function syncParentIcon() { // update icon on available item - angular.forEach(scope.availableChildren, function(availableChild){ - if(availableChild.id === scope.parentId) { - availableChild.icon = scope.parentIcon; + Utilities.forEach(scope.availableChildren, availableChild => { + if (availableChild.id === scope.parentId) { + availableChild.icon = scope.parentIcon; } }); // update icon on selected child - angular.forEach(scope.selectedChildren, function(selectedChild){ - if(selectedChild.id === scope.parentId) { - selectedChild.icon = scope.parentIcon; + Utilities.forEach(scope.selectedChildren, selectedChild => { + if (selectedChild.id === scope.parentId) { + selectedChild.icon = scope.parentIcon; } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js index fcc02f53a2..bf03749faa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgridselector.directive.js @@ -91,7 +91,7 @@ } // update selected items - angular.forEach(scope.selectedItems, function (selectedItem) { + Utilities.forEach(scope.selectedItems, selectedItem => { if (selectedItem.placeholder) { selectedItem.name = scope.name; @@ -99,12 +99,11 @@ if (scope.alias !== null && scope.alias !== undefined) { selectedItem.alias = scope.alias; } - } }); // update availableItems - angular.forEach(scope.availableItems, function (availableItem) { + Utilities.forEach(scope.availableItems, availableItem => { if (availableItem.placeholder) { availableItem.name = scope.name; @@ -112,7 +111,6 @@ if (scope.alias !== null && scope.alias !== undefined) { availableItem.alias = scope.alias; } - } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 06e1c61f1e..f09d815ae0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -25,7 +25,7 @@ // set placeholder property on each group if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { addInitProperty(group); }); } @@ -34,14 +34,16 @@ addInitGroup(scope.model.groups); activateFirstGroup(scope.model.groups); + + var labelKeys = [ + "validation_validation", + "contentTypeEditor_tabHasNoSortOrder" + ]; // localize texts - localizationService.localize("validation_validation").then(function (value) { - validationTranslated = value; - }); - - localizationService.localize("contentTypeEditor_tabHasNoSortOrder").then(function (value) { - tabNoSortOrderTranslated = value; + localizationService.localizeMany(labelKeys).then(data => { + validationTranslated = data[0]; + tabNoSortOrderTranslated = data[1]; }); } @@ -129,7 +131,6 @@ // store this tabs sort order as reference for the next prevSortOrder = group.sortOrder; - } }); @@ -179,12 +180,11 @@ function updatePropertiesSortOrder() { - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { if (group.tabState !== "init") { group.properties = contentTypeHelper.updatePropertiesSortOrder(group.properties); } }); - } function setupAvailableContentTypesModel(result) { @@ -242,7 +242,6 @@ scope.sortingMode = true; scope.sortingButtonKey = "general_reorderDone"; - } }; @@ -254,20 +253,19 @@ compositeContentTypes: scope.model.compositeContentTypes, view: "views/common/infiniteeditors/compositions/compositions.html", size: "small", - submit: function () { + submit: () => { // make sure that all tabs has an init property if (scope.model.groups.length !== 0) { - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { addInitProperty(group); }); } // remove overlay editorService.close(); - }, - close: function (oldModel) { + close: oldModel => { // reset composition changes scope.model.groups = oldModel.contentType.groups; @@ -277,7 +275,7 @@ editorService.close(); }, - selectCompositeContentType: function (selectedContentType) { + selectCompositeContentType: selectedContentType => { var deferred = $q.defer(); @@ -291,7 +289,7 @@ //use a different resource lookup depending on the content type type var resourceLookup = scope.contentType === "documentType" ? contentTypeResource.getById : mediaTypeResource.getById; - resourceLookup(selectedContentType.id).then(function (composition) { + resourceLookup(selectedContentType.id).then(composition => { //based on the above filtering we shouldn't be able to select an invalid one, but let's be safe and // double check here. var overlappingAliases = contentTypeHelper.validateAddingComposition(scope.model, composition); @@ -414,7 +412,7 @@ scope.activateGroup = function (selectedGroup) { // set all other groups that are inactive to active - angular.forEach(scope.model.groups, function (group) { + Utilities.forEach(scope.model.groups, group => { // skip init tab if (group.tabState !== "init") { group.tabState = "inActive"; @@ -452,7 +450,7 @@ // check i init tab already exists var addGroup = true; - angular.forEach(groups, function (group) { + Utilities.forEach(groups, group => { if (group.tabState === "init") { addGroup = false; } @@ -653,7 +651,7 @@ }; // check if there already is an init property - angular.forEach(group.properties, function (property) { + Utilities.forEach(group.properties, property => { if (property.propertyState === "init") { addInitPropertyBool = false; } @@ -669,8 +667,8 @@ function updateSameDataTypes(newProperty) { // find each property - angular.forEach(scope.model.groups, function (group) { - angular.forEach(group.properties, function (property) { + Utilities.forEach(scope.model.groups, group => { + Utilities.forEach(group.properties, property => { if (property.dataTypeId === newProperty.dataTypeId) { @@ -681,9 +679,7 @@ property.dataTypeId = newProperty.dataTypeId; property.dataTypeIcon = newProperty.dataTypeIcon; property.dataTypeName = newProperty.dataTypeName; - } - }); }); } @@ -691,8 +687,8 @@ function hasPropertyOfDataTypeId(dataTypeId) { // look at each property - var result = _.filter(scope.model.groups, function (group) { - return _.filter(group.properties, function (property) { + var result = _.filter(scope.model.groups, group => { + return _.filter(group.properties, property => { return (property.dataTypeId === dataTypeId); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index f7b634a710..b6ba1aaedb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -135,11 +135,11 @@ var found = false; scope.listViewAnimation = "out"; - angular.forEach(miniListViewsHistory, function(historyItem, index){ + Utilities.forEach(miniListViewsHistory, (historyItem, index) => { // We need to make sure we can compare the two id's. // Some id's are integers and others are strings. // Members have string ids like "all-members". - if(historyItem.node.id.toString() === ancestor.id.toString()) { + if (historyItem.node.id.toString() === ancestor.id.toString()) { // load the list view from history scope.miniListViews = []; scope.miniListViews.push(historyItem); @@ -149,7 +149,7 @@ } }); - if(!found) { + if (!found) { // if we can't find the view in the history - close the list view scope.exitMiniListView(); } @@ -161,7 +161,7 @@ scope.showBackButton = function() { // don't show the back button if the start node is a list view - if(scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) { + if (scope.node.metaData && scope.node.metaData.IsContainer || scope.node.isContainer) { return false; } else { return true; @@ -178,7 +178,7 @@ function makeBreadcrumb() { scope.breadcrumb = []; - angular.forEach(miniListViewsHistory, function(historyItem){ + Utilities.forEach(miniListViewsHistory, historyItem => { scope.breadcrumb.push(historyItem.node); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js index 6c65cb6e23..c22a56f0d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminisearch.component.js @@ -11,17 +11,20 @@ model: "=", onStartTyping: "&?", onSearch: "&?", - onBlur: "&?" + onBlur: "&?", + labelKey: "@?", + inputId: "@?" } }); - function UmbMiniSearchController($scope) { - + function UmbMiniSearchController($scope, localizationService) { + var vm = this; vm.onKeyDown = onKeyDown; vm.onChange = onChange; - + vm.$onInit = onInit; + var searchDelay = _.debounce(function () { $scope.$apply(function () { if (vm.onSearch) { @@ -29,7 +32,7 @@ } }); }, 500); - + function onKeyDown(evt) { //13: enter switch (evt.keyCode) { @@ -40,7 +43,7 @@ break; } } - + function onChange() { if (vm.onStartTyping) { vm.onStartTyping(); @@ -48,6 +51,21 @@ searchDelay(); } + function onInit() { + vm.inputId = vm.inputId || "search_" + String.CreateGuid(); + setText(); + } + + function setText() { + var keyToLocalize = vm.labelKey || 'general_search'; + + localizationService.localize(keyToLocalize).then(function (data) { + // If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [ + if(data.indexOf('[') === -1){ + vm.text = data; + } + }); + } } })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index f9b26c81a5..5c8ef162fa 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js @@ -15,7 +15,7 @@ var selectedTab = $scope.model.variants[0].tabs[0]; if ($scope.tabAlias) { - angular.forEach($scope.model.variants[0].tabs, function (tab) { + Utilities.forEach($scope.model.variants[0].tabs, tab => { if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) { selectedTab = tab; return; @@ -33,20 +33,19 @@ $scope.$broadcast("formSubmitting", { scope: $scope }); // Sync the values back - angular.forEach($scope.ngModel.variants[0].tabs, function (tab) { + Utilities.forEach($scope.ngModel.variants[0].tabs, tab => { if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) { - var localPropsMap = selectedTab.properties.reduce(function (map, obj) { + var localPropsMap = selectedTab.properties.reduce((map, obj) => { map[obj.alias] = obj; return map; }, {}); - angular.forEach(tab.properties, function (prop) { + Utilities.forEach(tab.properties, prop => { if (localPropsMap.hasOwnProperty(prop.alias)) { prop.value = localPropsMap[prop.alias].value; } }); - } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js index ed74f94f26..d5c791281c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbrangeslider.directive.js @@ -52,6 +52,7 @@ For extra details about options and events take a look here: https://refreshless @param {callback} onSlide (callback): onSlide gets triggered when the handle is being dragged. @param {callback} onSet (callback): onSet will trigger every time a slider stops changing. @param {callback} onChange (callback): onChange fires when a user stops sliding, or when a slider value is changed by 'tap'. +@param {callback} onDrag (callback): onDrag fires when a connect element between handles is being dragged, while ignoring other updates to the slider values. @param {callback} onStart (callback): onStart fires when a handle is clicked (mousedown, or the equivalent touch events). @param {callback} onEnd (callback): onEnd fires when a handle is released (mouseup etc), or when a slide is canceled due to other reasons. **/ @@ -71,6 +72,7 @@ For extra details about options and events take a look here: https://refreshless onSlide: '&?', onSet: '&?', onChange: '&?', + onDrag: '&?', onStart: '&?', onEnd: '&?' } @@ -181,6 +183,15 @@ For extra details about options and events take a look here: https://refreshless }); } + // bind hook for drag + if (ctrl.onDrag) { + sliderInstance.noUiSlider.on('drag', function (values, handle, unencoded, tap, positions) { + $timeout(function () { + ctrl.onDrag({ values: values, handle: handle, unencoded: unencoded, tap: tap, positions: positions }); + }); + }); + } + // bind hook for start if (ctrl.onStart) { sliderInstance.noUiSlider.on('start', function (values, handle, unencoded, tap, positions) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 79dfee059e..d80b884dab 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -64,8 +64,7 @@ angular.module("umbraco.directives") function _filesQueued(files, event) { //Push into the queue - angular.forEach(files, - function(file) { + Utilities.forEach(files, file => { if (_filterFile(file) === true) { @@ -81,7 +80,7 @@ angular.module("umbraco.directives") if (!scope.working) { // Upload not allowed if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) { - files.map(function(file) { + files.map(file => { file.uploadStatus = "error"; file.serverErrorMessage = "File type is not allowed here"; scope.rejected.push(file); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js index d43282715e..9381940c74 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/disabletabindex.directive.js @@ -29,7 +29,7 @@ angular.module("umbraco.directives") var childInputs = tabbableService.tabbable(mutation.target); //For each item in childInputs - override or set HTML attribute tabindex="-1" - angular.forEach(childInputs, function (element) { + Utilities.forEach(childInputs, element => { $(element).attr('tabindex', '-1'); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js index 0b743d0f10..7658b4e043 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/umbkeyboardlist.directive.js @@ -32,20 +32,20 @@ angular.module('umbraco.directives') return { restrict: 'A', - link: function (scope, element, attr) { + link: function (scope, element) { var listItems = []; var currentIndex = 0; var focusSet = false; - $timeout(function(){ + $timeout(function() { // get list of all links in the list listItems = element.find("li :tabbable"); }); // Handle keydown events function keydown(event) { - $timeout(function(){ + $timeout(function() { checkFocus(); // arrow down if (event.keyCode === 40) { @@ -62,7 +62,7 @@ angular.module('umbraco.directives') var found = false; // check if any element has focus - angular.forEach(listItems, function (item, index) { + Utilities.forEach(listItems, (item, index) => { if ($(item).is(":focus")) { // if an element already has focus set the // currentIndex so we navigate from that element @@ -109,7 +109,6 @@ angular.module('umbraco.directives') // Stop listening when scope is destroyed. scope.$on('$destroy', stopListening); - } }; }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js b/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js index 4f977cb1b2..e628e48306 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/backdrop.service.js @@ -35,11 +35,11 @@ */ function open(options) { - if(options && options.element) { + if (options && options.element) { args.element = options.element; } - if(options && options.disableEventsOnClick) { + if (options && options.disableEventsOnClick) { args.disableEventsOnClick = options.disableEventsOnClick; } @@ -58,11 +58,11 @@ * */ function close() { - args.opacity = null, - args.element = null, - args.elementPreventClick = false, - args.disableEventsOnClick = false, - args.show = false + args.opacity = null; + args.element = null; + args.elementPreventClick = false; + args.disableEventsOnClick = false; + args.show = false; eventsService.emit("appState.backdrop", args); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 3b98e8c629..88894ac47a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -118,19 +118,28 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - if(isLeftColumnOnTop){ - backdropService.close(); - leftColumn.removeClass(aboveClass); + var tourIsOpen = document.body.classList.contains("umb-tour-is-visible"); + if (tourIsOpen) { + return; + } + + var aboveClass = "above-backdrop"; + var leftColumn = document.getElementById("leftcolumn"); + + if(leftColumn) { + var isLeftColumnOnTop = leftColumn.classList.contains(aboveClass); + + if (isLeftColumnOnTop) { + backdropService.close(); + leftColumn.classList.remove(aboveClass); + } } } function showBackdrop() { var backDropOptions = { - 'element': $('#leftcolumn')[0] + 'element': document.getElementById('leftcolumn') }; backdropService.open(backDropOptions); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index ea05dad4e7..8a965f2c78 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -51,7 +51,12 @@ function close() { focusLockService.removeInertAttribute(); - backdropService.close(); + + var tourIsOpen = document.body.classList.contains("umb-tour-is-visible"); + if (!tourIsOpen) { + backdropService.close(); + } + currentOverlay = null; eventsService.emit("appState.overlay", null); } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js b/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js index 16ced17075..c6aceeb9e0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/retryqueue.service.js @@ -21,7 +21,7 @@ push: function (retryItem) { retryQueue.push(retryItem); // Call all the onItemAdded callbacks - angular.forEach(service.onItemAddedCallbacks, function (cb) { + Utilities.forEach(service.onItemAddedCallbacks, cb => { try { cb(retryItem); } catch (e) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index b83367ef6e..57776d44f3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -357,18 +357,22 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s return plugin.name; }); - //plugins that must always be active + // Plugins that must always be active plugins.push("autoresize"); plugins.push("noneditable"); + // Table plugin use color picker plugin in table properties + if (plugins.includes("table")) { + plugins.push("colorpicker"); + } + var modeTheme = ''; var modeInline = false; - - //Based on mode set - //classic = Theme: modern, inline: false - //inline = Theme: modern, inline: true, - //distraction-free = Theme: inlite, inline: true + // Based on mode set + // classic = Theme: modern, inline: false + // inline = Theme: modern, inline: true, + // distraction-free = Theme: inlite, inline: true switch (args.mode) { case "classic": modeTheme = "modern"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less index bd1b8ab07a..68a29df89e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-app-header.less @@ -48,6 +48,7 @@ opacity: 0.8; color: @white; font-size: 22px; + flex-shrink: 0; } .umb-app-header__button:hover .umb-app-header__action-icon, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less index 2f9430ef41..f0423e5d2e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less @@ -16,7 +16,7 @@ box-shadow: 0 10px 20px rgba(0,0,0,.12),0 6px 6px rgba(0,0,0,.14); } -.umb-search__label{ +.umb-search__label { margin: 0; } @@ -33,7 +33,7 @@ height: 70px; } -.umb-search-input.umb-search-input { +input.umb-search-input { width: 100%; height: 70px; border: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less index 7104e6478f..b6acb2f6cf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-ellipsis.less @@ -1,4 +1,4 @@ -.umb-button-ellipsis{ +.umb-button-ellipsis { padding: 0 5px; text-align: center; margin: 0 auto; @@ -28,13 +28,13 @@ } .umb-button-ellipsis--tab, - .umb-tour-is-visible .umb-tree &, + .umb-tour-is-visible .umb-tree .umb-tree-item.above-backdrop &, &:hover, - &:focus{ + &:focus { opacity: 1; } - &--hidden{ + &--hidden { opacity: 0; &:hover, @@ -55,7 +55,7 @@ .umb-button-ellipsis--tab & { margin: 0 0 7px; - } + } .umb-button-ellipsis--small & { font-size: 8px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less index 67038380ca..050f907acc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less @@ -1,5 +1,5 @@ .umb-notifications { - z-index: 1100; + z-index: 999999; position: absolute; bottom: @editorFooterHeight; left: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less index 8cf64e183c..bdfc55f648 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-color-swatches.less @@ -59,7 +59,7 @@ font-size: 14px; padding: 1px 5px; min-height: 45px; - max-width: 100%; + max-width: calc(100% - 8px); margin-top: auto; margin-bottom: -3px; margin-left: -1px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less index ec598c17eb..bdcc0055dd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-search.less @@ -1,25 +1,23 @@ .umb-mini-search { position: relative; - display: block; + display: flex; + justify-content: center; + align-items: center; .icon { position: absolute; - width: 30px; - height: 30px; - display: flex; - justify-content: center; - align-items: center; - margin: 1px; - padding: 0; + left: 9px; + width: 1em; + height: 1em; pointer-events: none; color: @ui-action-discreet-type; transition: color .1s linear; } input { - width: 0px; + width: 0; padding-left: 24px; - margin-bottom: 0px; + margin-bottom: 0; background-color: transparent; border-color: @ui-action-discreet-border; transition: background-color .1s linear, border-color .1s linear, color .1s linear, width .1s ease-in-out, padding-left .1s ease-in-out; @@ -30,6 +28,7 @@ .icon { color: @ui-action-discreet-type-hover; } + input { color: @ui-action-discreet-border-hover; border-color: @ui-action-discreet-border-hover; @@ -47,5 +46,4 @@ width: 190px; padding-left:30px; } - } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less index 83774e2dae..75d171dd87 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -20,7 +20,7 @@ } } - i.icon { + .icon { font-size: 55px; line-height: 70px } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 11d11c7e3a..87603671dd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -575,43 +575,31 @@ font-size: 22px; } -/* - .umb-cropper-gravity .viewport, .umb-cropper-gravity, .umb-cropper-imageholder { - display: inline-block; - max-width: 100%; +.umb-crop-thumbnail-container { + img { + max-width: unset; } +} - .umb-cropper-imageholder { - float: left; - } +.cropList { + display: inline-block; + position: relative; + vertical-align: top; + flex:0; +} - .umb-cropper-imageholder umb-image-gravity { - display:block; - } - */ +.umb-fileupload, +.umb-cropper-gravity { - .umb-crop-thumbnail-container { - img { - max-width: unset; - } - } - - .cropList { - display: inline-block; - position: relative; - vertical-align: top; - flex:0; - } - - .umb-cropper-gravity .gravity-container { + .gravity-container { border: 1px solid @inputBorder; - box-sizing: border-box; - line-height: 0; width: 100%; height: 100%; overflow: hidden; - .checkeredBackground(); + box-sizing: border-box; + line-height: 0; contain: content; + .checkeredBackground(); &:focus, &:focus-within { border-color: @inputBorderFocus; @@ -621,7 +609,6 @@ position: relative; width: 100%; height: 100%; - display: flex; justify-content: center; align-items: center; @@ -637,169 +624,169 @@ } } } - +} - .umb-cropper-gravity img { - position: relative; - max-width: 100%; - height: auto; - top: 0; - left: 0; - } +.umb-cropper-gravity img { + position: relative; + max-width: 100%; + height: auto; + top: 0; + left: 0; +} - .umb-cropper-gravity .overlayViewport { - position: absolute; - top:0; - bottom:0; - left:0; - right:0; - contain: strict; +.umb-cropper-gravity .overlayViewport { + position: absolute; + top:0; + bottom:0; + left:0; + right:0; + contain: strict; - display: flex; - justify-content: center; - align-items: center; - } - .umb-cropper-gravity .overlay { - position: relative; - display: block; - max-width: 100%; - max-height: 100%; - cursor: crosshair; - } - .umb-cropper-gravity .overlay .focalPoint { - position: absolute; - top: 0; - left: 0; - cursor: move; - z-index: @zindexCropperOverlay; + display: flex; + justify-content: center; + align-items: center; +} +.umb-cropper-gravity .overlay { + position: relative; + display: block; + max-width: 100%; + max-height: 100%; + cursor: crosshair; +} +.umb-cropper-gravity .overlay .focalPoint { + position: absolute; + top: 0; + left: 0; + cursor: move; + z-index: @zindexCropperOverlay; - width: 14px; - height: 14px; - // this element should have no width or height as its preventing the jQuery draggable-plugin to go all the way to the sides: - margin-left: -10px; - margin-top: -10px; - margin-right: -10px; - margin-bottom: -10px; + width: 14px; + height: 14px; + // this element should have no width or height as its preventing the jQuery draggable-plugin to go all the way to the sides: + margin-left: -10px; + margin-top: -10px; + margin-right: -10px; + margin-bottom: -10px; - text-align: center; - border-radius: 20px; - background: @pinkLight; - border: 3px solid @white; - opacity: 0.8; - } + text-align: center; + border-radius: 20px; + background: @pinkLight; + border: 3px solid @white; + opacity: 0.8; +} - .umb-cropper-gravity .overlay .focalPoint i { - font-size: 26px; - line-height: 26px; - opacity: 0.8 !important; - } +.umb-cropper-gravity .overlay .focalPoint i { + font-size: 26px; + line-height: 26px; + opacity: 0.8 !important; +} - .imagecropper { - display: flex; - align-items: flex-start; - flex-direction: row; +.imagecropper { + display: flex; + align-items: flex-start; + flex-direction: row; - @media (max-width: 768px) { - flex-direction: column; - } - - } - - .imagecropper .umb-cropper__container { - position: relative; - width: 100%; - } - - .imagecropper .umb-cropper__container .button-drawer { - display: flex; - justify-content: flex-end; - padding: 10px; - position: relative; - - button { - margin-left: 4px; - } - } - - .umb-close-cropper { - position: absolute; - top: 3px; - right: 3px; - cursor: pointer; - z-index: @zindexCropperOverlay + 1; - } - - .umb-close-cropper:hover { - opacity: .9; - background: @gray-10; - } - - .imagecropper .umb-sortable-thumbnails { - display: flex; - flex-direction: row; - flex-wrap: wrap; - } - - .imagecropper .umb-sortable-thumbnails li { - display: flex; + @media (max-width: 768px) { flex-direction: column; - justify-content: space-between; - padding: 8px; + } + +} + +.imagecropper .umb-cropper__container { + position: relative; + width: 100%; +} + +.imagecropper .umb-cropper__container .button-drawer { + display: flex; + justify-content: flex-end; + padding: 10px; + position: relative; + + button { + margin-left: 4px; + } +} + +.umb-close-cropper { + position: absolute; + top: 3px; + right: 3px; + cursor: pointer; + z-index: @zindexCropperOverlay + 1; +} + +.umb-close-cropper:hover { + opacity: .9; + background: @gray-10; +} + +.imagecropper .umb-sortable-thumbnails { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.imagecropper .umb-sortable-thumbnails li { + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 8px; + margin-top: 0; +} + +.imagecropper .umb-sortable-thumbnails li.current { + border-color: @gray-8; + background: @gray-10; + color: @black; + cursor: pointer; +} + +.imagecropper .umb-sortable-thumbnails li:hover, +.imagecropper .umb-sortable-thumbnails li.current:hover { + border-color: @gray-8; + background: @gray-10; + color: @black; + cursor: pointer; + opacity: .95; +} + +.imagecropper .umb-sortable-thumbnails li .crop-name, +.imagecropper .umb-sortable-thumbnails li .crop-size, +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + display: block; + text-align: left; + font-size: 13px; + line-height: 1; +} + +.imagecropper .umb-sortable-thumbnails li .crop-name { + font-weight: bold; + margin: 10px 0 5px; +} + +.imagecropper .umb-sortable-thumbnails li .crop-size, +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + font-size: 10px; + font-style: italic; + margin: 0 0 5px; +} + +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + color: @gray-6; +} + +.btn-crop-delete { + display: block; + text-align: left; +} + +.imagecropper .cropList-container { + h5 { + margin-left: 10px; margin-top: 0; } - - .imagecropper .umb-sortable-thumbnails li.current { - border-color: @gray-8; - background: @gray-10; - color: @black; - cursor: pointer; - } - - .imagecropper .umb-sortable-thumbnails li:hover, - .imagecropper .umb-sortable-thumbnails li.current:hover { - border-color: @gray-8; - background: @gray-10; - color: @black; - cursor: pointer; - opacity: .95; - } - - .imagecropper .umb-sortable-thumbnails li .crop-name, - .imagecropper .umb-sortable-thumbnails li .crop-size, - .imagecropper .umb-sortable-thumbnails li .crop-annotation { - display: block; - text-align: left; - font-size: 13px; - line-height: 1; - } - - .imagecropper .umb-sortable-thumbnails li .crop-name { - font-weight: bold; - margin: 10px 0 5px; - } - - .imagecropper .umb-sortable-thumbnails li .crop-size, - .imagecropper .umb-sortable-thumbnails li .crop-annotation { - font-size: 10px; - font-style: italic; - margin: 0 0 5px; - } - - .imagecropper .umb-sortable-thumbnails li .crop-annotation { - color: @gray-6; - } - - .btn-crop-delete { - display: block; - text-align: left; - } - - .imagecropper .cropList-container { - h5 { - margin-left: 10px; - margin-top: 0; - } - } +} // // Folder browser diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less index d2968696dc..ada33c2d63 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -40,6 +40,14 @@ margin-left: auto; margin-right: auto; } + +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + +.mt-auto { margin-top: auto; } +.mb-auto { margin-bottom: auto; } .ml-auto { margin-left: auto; } .mr-auto { margin-right: auto; } diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js index abbc287e0f..14e37ecb87 100644 --- a/src/Umbraco.Web.UI.Client/src/utilities.js +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -110,7 +110,7 @@ * Not equivalent to angular.forEach. But like the angularJS method this does not fail on null or undefined. */ const forEach = (obj, iterator) => { - if (obj) { + if (obj && isArray(obj)) { return obj.forEach(iterator); } return obj; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html index b72de0960d..273599343d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html @@ -18,7 +18,7 @@
    i.parentId === child.id); listViewResults.forEach(item => { + if (!child.children) return; + var childExists = child.children.find(c => c.id === item.id); if (!childExists) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html index 252c1b7cd5..dbddb141dd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/usergrouppicker/usergrouppicker.html @@ -18,7 +18,7 @@
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index d5f9a0e363..0b976688c0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -86,13 +86,13 @@
    -
    @@ -130,13 +130,11 @@
    -
    +
    +
    {{tab.label}}
    -
    -

    {{property.caption}}

    -
    -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html index da1f61ee4a..1ff3960ae9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html @@ -10,7 +10,7 @@
    -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 5f8967c419..e4fdb7fc33 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -157,17 +157,18 @@ - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-search-filter.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-search-filter.html index ab21654f91..6a17f33c50 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-search-filter.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-search-filter.html @@ -5,6 +5,8 @@
    - +

    {{ miniListView.node.name }}

    @@ -32,19 +32,17 @@
    - diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html index 3c95dbae5b..57048d8b5c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-search.html @@ -1,6 +1,10 @@ - + + - Edit - {{name}} + Edit {{name}} - Open - {{name}} + Open {{name}} - Remove - {{name}} + Remove {{name}}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umb-property-info-button.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umb-property-info-button.html index aca2858070..cb15a7ba93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umb-property-info-button.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umb-property-info-button.html @@ -1,5 +1,9 @@ -
    + ng-click="vm.onMouseClick($event)" + ng-bind="vm.symbol"> + +
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umbpropertyinfobutton.component.js b/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umbpropertyinfobutton.component.js index e160071ea2..d4bd3729bd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umbpropertyinfobutton.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-property-info-button/umbpropertyinfobutton.component.js @@ -10,24 +10,33 @@ transclude: true, bindings: { buttonTitle: "@?", + buttonTitleKey: "@?", symbol: "@?" } }); - function UmbPropertyInfoButtonController() { + function UmbPropertyInfoButtonController(localizationService) { var vm = this; + vm.show = false; vm.onMouseClick = function ($event) { vm.show = !vm.show; }; + vm.onMouseClickOutside = function ($event) { vm.show = false; }; vm.$onInit = function() { vm.symbol = vm.symbol || "i"; + + if (vm.buttonTitleKey) { + localizationService.localize(vm.buttonTitleKey).then(value => { + vm.buttonTitle = value; + }); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html index fadc0ac3b1..1db179aa42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-property-file-upload.html @@ -4,7 +4,7 @@
    - +

    Click to upload

    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html index 20718cf804..003ea7ff76 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html @@ -1,7 +1,7 @@
    - - - + + +
    {{ name }}
    @@ -20,7 +20,7 @@ {{ contentStartNode.name }}
    - +
    Media start node: diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js index 2de526b503..a69de224dd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js @@ -121,11 +121,25 @@ * @param {any} type publish or unpublish */ function datePickerShow(variant, type) { + var activeDatePickerInstance; if (type === 'publish') { variant.releaseDatePickerOpen = true; + activeDatePickerInstance = variant.releaseDatePickerInstance; } else if (type === 'unpublish') { variant.expireDatePickerOpen = true; + activeDatePickerInstance = variant.expireDatePickerInstance; } + + // Prevent enter key in time fields from submitting the overlay before the associated input gets the updated time + if (activeDatePickerInstance && !activeDatePickerInstance.hourElement.hasAttribute("overlay-submit-on-enter")) + { + activeDatePickerInstance.hourElement.setAttribute("overlay-submit-on-enter", "false"); + } + if (activeDatePickerInstance && !activeDatePickerInstance.minuteElement.hasAttribute("overlay-submit-on-enter")) + { + activeDatePickerInstance.minuteElement.setAttribute("overlay-submit-on-enter", "false"); + } + checkForBackdropClick(); $scope.model.disableSubmitButton = !canSchedule(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index c153e5ecb2..e08451f261 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -88,9 +88,9 @@ function startUpDynamicContentController($q, $timeout, $scope, dashboardResource evts.push(eventsService.on("appState.tour.complete", function (name, completedTour) { $timeout(function(){ - angular.forEach(vm.tours, function (tourGroup) { - angular.forEach(tourGroup, function (tour) { - if(tour.alias === completedTour.alias) { + Utilities.forEach(vm.tours, tourGroup => { + Utilities.forEach(tourGroup, tour => { + if (tour.alias === completedTour.alias) { tour.completed = true; } }); @@ -100,24 +100,24 @@ function startUpDynamicContentController($q, $timeout, $scope, dashboardResource //proxy remote css through the local server assetsService.loadCss(dashboardResource.getRemoteDashboardCssUrl("content"), $scope); - dashboardResource.getRemoteDashboardContent("content").then( - function (data) { - vm.loading = false; + dashboardResource.getRemoteDashboardContent("content").then(data => { - //test if we have received valid data - //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code - if (data && data.sections) { - vm.dashboard = data; - } else { - vm.showDefault = true; - } - }, - function (exception) { - console.error(exception); - vm.loading = false; + vm.loading = false; + + //test if we have received valid data + //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code + if (data && data.sections) { + vm.dashboard = data; + } else { vm.showDefault = true; - }); + } + }, + function (exception) { + console.error(exception); + vm.loading = false; + vm.showDefault = true; + }); onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.controller.js index 97f8b6bd79..94e81afb8f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.controller.js @@ -1,4 +1,4 @@ -function ExamineManagementController($scope, $http, $q, $timeout, $location, umbRequestHelper, localizationService, overlayService, editorService) { +function ExamineManagementController($http, $q, $timeout, umbRequestHelper, localizationService, overlayService, editorService) { var vm = this; @@ -166,17 +166,17 @@ function ExamineManagementController($scope, $http, $q, $timeout, $location, umb vm.searchResults.totalPages = Math.ceil(vm.searchResults.totalRecords / 20); // add URLs to edit well known entities _.each(vm.searchResults.results, function (result) { - var section = result.values["__IndexType"]; + var section = result.values["__IndexType"][0]; switch (section) { case "content": case "media": - result.editUrl = "/" + section + "/" + section + "/edit/" + result.values["__NodeId"]; - result.editId = result.values["__NodeId"]; + result.editUrl = "/" + section + "/" + section + "/edit/" + result.values["__NodeId"][0]; + result.editId = result.values["__NodeId"][0]; result.editSection = section; break; case "member": - result.editUrl = "/member/member/edit/" + result.values["__Key"]; - result.editId = result.values["__Key"]; + result.editUrl = "/member/member/edit/" + result.values["__Key"][0]; + result.editId = result.values["__Key"][0]; result.editSection = section; break; } diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html index e009bd2f40..d1162b61eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/examinemanagement.html @@ -23,8 +23,10 @@
    - + +
    @@ -59,7 +61,7 @@
    - +
    @@ -116,15 +118,19 @@
    -