From e775497c5ef07b5c2da8f364d2a699c9c54a65d7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 16:09:52 +1000 Subject: [PATCH 01/68] Removes ReaderWriterLockSlim from PropertyGroupCollection and PropertyTypeCollection. No lock should exist here at all not even sure why it's there (Based on 9yr old code). I'm pretty sure it's there because these entities used to be cached (and not cloned) which meant it was the same instance used between threads. --- .../Models/PropertyGroupCollection.cs | 55 +++++++------------ .../Models/PropertyTypeCollection.cs | 45 ++++++--------- 2 files changed, 37 insertions(+), 63 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 973147b3fbe8..e2af5835f21f 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -4,7 +4,6 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; namespace Umbraco.Core.Models { @@ -17,8 +16,6 @@ namespace Umbraco.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(); - internal PropertyGroupCollection() { } @@ -70,47 +67,37 @@ protected override void ClearItems() internal 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(); + var exists = Contains(item.Id); + 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"); - //Note this is done to ensure existing groups can be renamed - if (item.HasIdentity && item.Id > 0) + //collection events will be raised in SetItem + SetItem(IndexOfKey(item.Id), item); + return; + } + } + else + { + var key = GetKeyForItem(item); + if (key != null) { - var exists = Contains(item.Id); + 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 cc9d00fa5d8c..b7a735e5dc43 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -4,7 +4,6 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; -using System.Threading; namespace Umbraco.Core.Models { @@ -16,10 +15,6 @@ namespace Umbraco.Core.Models // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { - [IgnoreDataMember] - private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); - - internal PropertyTypeCollection(bool supportsPublishing) { SupportsPublishing = supportsPublishing; @@ -87,36 +82,28 @@ protected override void ClearItems() 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)) + var key = GetKeyForItem(item); + if (key != null) + { + var exists = Contains(key); + if (exists) { - //make it the next iteration - item.SortOrder = this.Max(x => x.SortOrder) + 1; + //collection events will be raised in SetItem + SetItem(IndexOfKey(key), item); + return; } - - //collection events will be raised in InsertItem - base.Add(item); } - finally + + //check if the item's sort order is already in use + if (this.Any(x => x.SortOrder == item.SortOrder)) { - if (_addLocker.IsWriteLockHeld) - _addLocker.ExitWriteLock(); + //make it the next iteration + item.SortOrder = this.Max(x => x.SortOrder) + 1; } + + //collection events will be raised in InsertItem + base.Add(item); } /// From b75a7865519bd27f311ab7bba9746864d969dd32 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 16:41:11 +1000 Subject: [PATCH 02/68] Ensures the ReaderWriterLockSlim within our caches are disposed at the end of the app --- src/Umbraco.Core/Cache/AppCaches.cs | 25 ++++++++++++++++- .../Cache/AppPolicedCacheDictionary.cs | 27 +++++++++++++++++-- src/Umbraco.Core/Cache/DeepCloneAppCache.cs | 23 +++++++++++++++- src/Umbraco.Core/Cache/ObjectCacheAppCache.cs | 21 ++++++++++++++- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 22 ++++++++++++++- 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs index fbfc4c8c82c1..05453663f5fc 100644 --- a/src/Umbraco.Core/Cache/AppCaches.cs +++ b/src/Umbraco.Core/Cache/AppCaches.cs @@ -6,8 +6,10 @@ namespace Umbraco.Core.Cache /// /// Represents the application caches. /// - public class AppCaches + public class AppCaches : IDisposable { + private bool _disposedValue; + /// /// Initializes a new instance of the for use in a web application. /// @@ -82,5 +84,26 @@ public AppCaches( /// search through all keys on a global scale. /// public IsolatedCaches IsolatedCaches { get; } + + 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 5c60dededaa4..4a6c032e4f19 100644 --- a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs +++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs @@ -7,7 +7,7 @@ namespace Umbraco.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 +24,7 @@ protected AppPolicedCacheDictionary(Func cacheFactory) /// Gets the internal cache factory, for tests only! /// internal readonly Func CacheFactory; + private bool _disposedValue; /// /// Gets or creates a cache. @@ -70,5 +71,27 @@ public void ClearAllCaches() 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); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index eff06e2aad90..10a83d742346 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -12,8 +12,10 @@ namespace Umbraco.Core.Cache /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, /// when the item is deep-cloneable. /// - internal class DeepCloneAppCache : IAppPolicyCache + internal class DeepCloneAppCache : IAppPolicyCache, IDisposable { + private bool _disposedValue; + /// /// Initializes a new instance of the class. /// @@ -153,5 +155,24 @@ private static object CheckCloneableAndTracksChanges(object input) 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 5c4f76f51dcd..fd1e7f21f160 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -13,9 +13,10 @@ namespace Umbraco.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 . @@ -360,5 +361,23 @@ private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSlidin } 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/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index bec596b12941..d7377a193a6f 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Cache /// Implements on top of a . /// /// The underlying cache is expected to be HttpRuntime.Cache. - internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache + internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache, IDisposable { // locker object that supports upgradeable read locking // does not need to support recursion if we implement the cache correctly and ensure @@ -19,6 +19,7 @@ internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicyCache private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); private readonly System.Web.Caching.Cache _cache; + private bool _disposedValue; /// /// Initializes a new instance of the class. @@ -204,5 +205,24 @@ private void InsertInternal(string cacheKey, Func getCacheItem, TimeSpan _locker.ExitWriteLock(); } } + + 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); + } } } From 7e332b23c9852a827f8ba7691ed2fc3161d0d07c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 17:14:34 +1000 Subject: [PATCH 03/68] Fixes ConcurrentHashSet to just use a ConcurrentDictionary as it's underlying source and remove the ReaderWriterLockSlim since we are never disposing this. Makes IdkMap disposable. --- .../Collections/ConcurrentHashSet.cs | 109 +++--------------- src/Umbraco.Core/Services/IdkMap.cs | 29 ++++- 2 files changed, 41 insertions(+), 97 deletions(-) diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index c4dba51acd5b..0424c4dae1e4 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,8 +1,8 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Umbraco.Core.Collections { @@ -14,9 +14,10 @@ namespace Umbraco.Core.Collections [Serializable] public class ConcurrentHashSet : ICollection { - private readonly HashSet _innerSet = new HashSet(); - private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - + // Internally we just use a ConcurrentDictionary with the same value for the Value (since we don't actually care about the value) + private static readonly byte _emptyValue = 0x0; + private readonly ConcurrentDictionary _innerSet = new ConcurrentDictionary(); + /// /// Returns an enumerator that iterates through the collection. /// @@ -26,7 +27,7 @@ public class ConcurrentHashSet : ICollection /// 1 public IEnumerator GetEnumerator() { - return GetThreadSafeClone().GetEnumerator(); + return _innerSet.Keys.GetEnumerator(); } /// @@ -50,16 +51,7 @@ IEnumerator IEnumerable.GetEnumerator() /// The object to remove from the .The is read-only. public bool Remove(T item) { - try - { - _instanceLocker.EnterWriteLock(); - return _innerSet.Remove(item); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + return _innerSet.TryRemove(item, out _); } @@ -74,17 +66,7 @@ public int Count { get { - try - { - _instanceLocker.EnterReadLock(); - return _innerSet.Count; - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } - + return _innerSet.Count; } } @@ -99,19 +81,10 @@ public int Count /// /// Adds an item to the . /// - /// The object to add to the .The is read-only. + /// The object to add to the . public void Add(T item) { - try - { - _instanceLocker.EnterWriteLock(); - _innerSet.Add(item); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + _innerSet.TryAdd(item, _emptyValue); } /// @@ -121,21 +94,7 @@ public void Add(T item) /// public bool TryAdd(T item) { - if (Contains(item)) return false; - try - { - _instanceLocker.EnterWriteLock(); - - //double check - if (_innerSet.Contains(item)) return false; - _innerSet.Add(item); - return true; - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + return _innerSet.TryAdd(item, _emptyValue); } /// @@ -144,16 +103,7 @@ public bool TryAdd(T item) /// The is read-only. public void Clear() { - try - { - _instanceLocker.EnterWriteLock(); - _innerSet.Clear(); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + _innerSet.Clear(); } /// @@ -165,16 +115,7 @@ public void Clear() /// The object to locate in the . public bool Contains(T item) { - try - { - _instanceLocker.EnterReadLock(); - return _innerSet.Contains(item); - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } + return _innerSet.ContainsKey(item); } /// @@ -183,24 +124,8 @@ public bool Contains(T item) /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int index) { - var clone = GetThreadSafeClone(); - clone.CopyTo(array, index); - } - - private HashSet GetThreadSafeClone() - { - HashSet clone = null; - try - { - _instanceLocker.EnterReadLock(); - clone = new HashSet(_innerSet, _innerSet.Comparer); - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } - return clone; + var keys = _innerSet.Keys; + keys.CopyTo(array, index); } /// @@ -209,8 +134,8 @@ private HashSet GetThreadSafeClone() /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 public void CopyTo(Array array, int index) { - var clone = GetThreadSafeClone(); - Array.Copy(clone.ToArray(), 0, array, index, clone.Count); + var keys = _innerSet.Keys; + Array.Copy(keys.ToArray(), 0, array, index, keys.Count); } } } diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index f352def49e70..b1caa1ea5961 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Services { - public class IdkMap + public class IdkMap : IDisposable { private readonly IScopeProvider _scopeProvider; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); @@ -46,6 +46,7 @@ public IdkMap(IScopeProvider scopeProvider) private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary = new ConcurrentDictionary id2key, Func key2id)>(); + private bool _disposedValue; internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id) { @@ -106,8 +107,8 @@ private Attempt PopulateAndGetIdForKey(Guid key, UmbracoObjectTypes umbraco } finally { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); } } @@ -125,8 +126,8 @@ private Attempt PopulateAndGetKeyForId(int id, UmbracoObjectTypes umbracoO } finally { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); } } #endif @@ -369,5 +370,23 @@ public TypedId(T id, UmbracoObjectTypes umbracoObjectType) 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); + } } } From bf7a3251d80b2855605c6cf9b7c1c1e73712349a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 17:56:55 +1000 Subject: [PATCH 04/68] Ensure more ReaderWriterLockSlim are disposed or converted --- .../PureLiveModelFactory.cs | 27 ++++++++++++-- .../Logging/WebProfilerProvider.cs | 22 ++++-------- .../PublishedCache/NuCache/MemberCache.cs | 22 +++++++++++- .../NuCache/PublishedSnapshot.cs | 1 + .../PublishedContentTypeCache.cs | 23 +++++++++++- src/Umbraco.Web/Routing/SiteDomainHelper.cs | 36 ++++++++++++++----- 6 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index 8ef99383a417..5b5f54cdc812 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -21,7 +21,7 @@ namespace Umbraco.ModelsBuilder.Embedded { - internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject + internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject, IDisposable { private Assembly _modelsAssembly; private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary() }; @@ -33,6 +33,7 @@ internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredOb private int _ver, _skipver; private readonly int _debugLevel; private BuildManager _theBuildManager; + private bool _disposedValue; private readonly Lazy _umbracoServices; // fixme: this is because of circular refs :( private UmbracoServices UmbracoServices => _umbracoServices.Value; @@ -677,11 +678,31 @@ private void WatcherOnChanged(object sender, FileSystemEventArgs args) public void Stop(bool immediate) { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); + Dispose(); HostingEnvironment.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); + } + #endregion } } diff --git a/src/Umbraco.Web/Logging/WebProfilerProvider.cs b/src/Umbraco.Web/Logging/WebProfilerProvider.cs index f38a606745b9..8dad5f5b96a1 100755 --- a/src/Umbraco.Web/Logging/WebProfilerProvider.cs +++ b/src/Umbraco.Web/Logging/WebProfilerProvider.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Logging /// internal class WebProfilerProvider : AspNetRequestProvider { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + private readonly object _locker = new object(); private MiniProfiler _startupProfiler; private int _first; private volatile BootPhase _bootPhase; @@ -39,8 +39,7 @@ private enum BootPhase public void BeginBootRequest() { - _locker.EnterWriteLock(); - try + lock (_locker) { if (_bootPhase != BootPhase.Boot) throw new InvalidOperationException("Invalid boot phase."); @@ -48,28 +47,19 @@ public void BeginBootRequest() // assign the profiler to be the current MiniProfiler for the request // is's already active, starting and all - HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; - } - finally - { - _locker.ExitWriteLock(); + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; } } public void EndBootRequest() { - _locker.EnterWriteLock(); - try + lock (_locker) { if (_bootPhase != BootPhase.BootRequest) - throw new InvalidOperationException("Invalid boot phase."); + throw new InvalidOperationException("Invalid boot phase."); _bootPhase = BootPhase.Booted; - _startupProfiler = null; - } - finally - { - _locker.ExitWriteLock(); + _startupProfiler = null; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs index 2e196f629edc..f311a7f302c9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MemberCache.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { - internal class MemberCache : IPublishedMemberCache, INavigableData + internal class MemberCache : IPublishedMemberCache, INavigableData, IDisposable { private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; @@ -23,6 +23,7 @@ internal class MemberCache : IPublishedMemberCache, INavigableData private readonly IMemberService _memberService; private readonly PublishedContentTypeCache _contentTypeCache; private readonly bool _previewDefault; + private bool _disposedValue; public MemberCache(bool previewDefault, IAppCache snapshotCache, IMemberService memberService, PublishedContentTypeCache contentTypeCache, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IEntityXmlSerializer entitySerializer) @@ -158,6 +159,25 @@ public IPublishedContentType GetContentType(string alias) return _contentTypeCache.Get(PublishedItemType.Member, alias); } + 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.Web/PublishedCache/NuCache/PublishedSnapshot.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs index 3f5c1aa4d5fa..85bad38ac7dc 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshot.cs @@ -32,6 +32,7 @@ public void Dispose() { ContentCache.Dispose(); MediaCache.Dispose(); + MemberCache.Dispose(); } } diff --git a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs index 0f6e9af6bd47..c9db02f09140 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.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(); @@ -320,6 +320,8 @@ internal Func GetPublishedContentTypeByAlias // 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; @@ -367,5 +369,24 @@ private static string GetAliasKey(IPublishedContentType contentType) { 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.Web/Routing/SiteDomainHelper.cs b/src/Umbraco.Web/Routing/SiteDomainHelper.cs index 6173dfb43c26..26937b2899a6 100644 --- a/src/Umbraco.Web/Routing/SiteDomainHelper.cs +++ b/src/Umbraco.Web/Routing/SiteDomainHelper.cs @@ -4,13 +4,14 @@ using System.Threading; using System.Text.RegularExpressions; using Umbraco.Core; +using System.ComponentModel; namespace Umbraco.Web.Routing { /// /// Provides utilities to handle site domains. /// - public class SiteDomainHelper : ISiteDomainHelper + public class SiteDomainHelper : ISiteDomainHelper, IDisposable { #region Configure @@ -18,6 +19,7 @@ public class SiteDomainHelper : ISiteDomainHelper private static Dictionary _sites; private static Dictionary> _bindings; private static Dictionary> _qualifiedSites; + private bool _disposedValue; // these are for unit tests *only* // ReSharper disable ConvertToAutoPropertyWithPrivateSetter @@ -30,16 +32,10 @@ public class SiteDomainHelper : ISiteDomainHelper private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; private static readonly Regex DomainValidation = new Regex(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); - /// - /// Returns a disposable object that represents safe write access to config. - /// - /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. + [EditorBrowsable(EditorBrowsableState.Never)] protected static IDisposable ConfigWriteLock => new WriteLock(ConfigLock); - /// - /// Returns a disposable object that represents safe read access to config. - /// - /// Should be used in a using(SiteDomainHelper.ConfigWriteLock) { ... } mode. + [EditorBrowsable(EditorBrowsableState.Never)] protected static IDisposable ConfigReadLock => new ReadLock(ConfigLock); /// @@ -313,6 +309,28 @@ private static DomainAndUri MapDomain(IReadOnlyCollection domainAn return ret; } + #endregion + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + // This is pretty nasty disposing a static on an instance but it's because this whole class + // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. + ConfigLock.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + } } } From dc089970408355eb7a34d8e3901e036caa070fd5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Apr 2021 18:02:23 +1000 Subject: [PATCH 05/68] Removes usages of UpgradeableReadLock --- src/Umbraco.Core/Cache/ObjectCacheAppCache.cs | 23 +++++++++++--- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 31 ++++++++++++++----- src/Umbraco.Core/ReadLock.cs | 13 +------- src/Umbraco.Core/UpgradeableReadLock.cs | 10 +----- src/Umbraco.Core/WriteLock.cs | 13 +------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index fd1e7f21f160..e2f601760872 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -110,19 +110,34 @@ public object Get(string key, Func factory, TimeSpan? timeout, bool isSl Lazy result; - using (var lck = new UpgradeableReadLock(_locker)) + try { + _locker.EnterUpgradeableReadLock(); + result = MemoryCache.Get(key) as Lazy; if (result == null || FastDictionaryAppCacheBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { result = FastDictionaryAppCacheBase.GetSafeLazy(factory); var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles); - lck.UpgradeToWriteLock(); - //NOTE: This does an add or update - MemoryCache.Set(key, result, policy); + try + { + _locker.EnterWriteLock(); + //NOTE: This does an add or update + MemoryCache.Set(key, result, policy); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } } } + finally + { + if (_locker.IsUpgradeableReadLockHeld) + _locker.ExitUpgradeableReadLock(); + } //return result.Value; diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index d7377a193a6f..671cd2d9c415 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -139,8 +139,10 @@ private object GetInternal(string key, Func factory, TimeSpan? timeout, var value = result == null ? null : GetSafeLazyValue(result); if (value != null) return value; - using (var lck = new UpgradeableReadLock(_locker)) + try { + _locker.EnterUpgradeableReadLock(); + result = _cache.Get(key) as Lazy; // null if key not found // cannot create value within the lock, so if result.IsValueCreated is false, just @@ -153,15 +155,28 @@ private object GetInternal(string key, Func factory, TimeSpan? timeout, var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); - lck.UpgradeToWriteLock(); - - // create a cache dependency if one is needed. - var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; - - //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); + try + { + _locker.EnterWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! + _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); + } + finally + { + if (_locker.IsWriteLockHeld) + _locker.ExitWriteLock(); + } } } + finally + { + if (_locker.IsUpgradeableReadLockHeld) + _locker.ExitUpgradeableReadLock(); + } // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache // exceptions (but try again and again) and silently eat them - however at diff --git a/src/Umbraco.Core/ReadLock.cs b/src/Umbraco.Core/ReadLock.cs index 9d3ef2216821..f830d08bfadc 100644 --- a/src/Umbraco.Core/ReadLock.cs +++ b/src/Umbraco.Core/ReadLock.cs @@ -1,20 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Umbraco.Core { - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// This is a very inefficient way to lock as it allocates one object each time we lock, - /// so it's OK to use this class for things that happen once, where it is convenient, but not - /// for performance-critical code! - /// + [Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")] public class ReadLock : IDisposable { private readonly ReaderWriterLockSlim _rwLock; diff --git a/src/Umbraco.Core/UpgradeableReadLock.cs b/src/Umbraco.Core/UpgradeableReadLock.cs index e3717fdf9ad7..656470cd8adc 100644 --- a/src/Umbraco.Core/UpgradeableReadLock.cs +++ b/src/Umbraco.Core/UpgradeableReadLock.cs @@ -1,17 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Umbraco.Core { - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// + [Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")] public class UpgradeableReadLock : IDisposable { private readonly ReaderWriterLockSlim _rwLock; diff --git a/src/Umbraco.Core/WriteLock.cs b/src/Umbraco.Core/WriteLock.cs index dfa170218b14..1b698dc59c27 100644 --- a/src/Umbraco.Core/WriteLock.cs +++ b/src/Umbraco.Core/WriteLock.cs @@ -1,20 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Umbraco.Core { - /// - /// Provides a convenience methodology for implementing locked access to resources. - /// - /// - /// Intended as an infrastructure class. - /// This is a very inefficient way to lock as it allocates one object each time we lock, - /// so it's OK to use this class for things that happen once, where it is convenient, but not - /// for performance-critical code! - /// + [Obsolete("Use ReaderWriterLockSlim directly. This will be removed in future versions.")] public class WriteLock : IDisposable { private readonly ReaderWriterLockSlim _rwLock; From 5d8fb9670bd75b4a002017b0dcd849c1fa159497 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Tue, 29 Jun 2021 00:37:19 +0200 Subject: [PATCH 06/68] Textstring prevalue editor view: Set id attribute (#10453) * Add missing focus styling * Set id attribute Co-authored-by: BatJan Co-authored-by: Jan Skovgaard Olsen --- .../src/views/prevalueeditors/textstring.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html index 717b9a8ce24a..5d613881b11a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/textstring.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From a19b79553d16dad705c62804df9adcad7312bac8 Mon Sep 17 00:00:00 2001 From: David Challener <694674+dchallener@users.noreply.github.com> Date: Tue, 29 Jun 2021 16:12:45 +0100 Subject: [PATCH 07/68] Update contributing.md Visual Studio min version Having had an older version of visual studio running I've been unable to build the source without making changes to the solution. However, upgrading to at least VS 16.8 has meant those changes haven't been required. This is to do with how the language target "latest" was working. Propose upping the min version listed here to avoid build issues being raised. --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3432ac472aa7..bada94c30b8d 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/) From 33e44181c1d044f4c5a04a3a495290725df69d2a Mon Sep 17 00:00:00 2001 From: David Challener <694674+dchallener@users.noreply.github.com> Date: Tue, 29 Jun 2021 22:05:18 +0100 Subject: [PATCH 08/68] Disable cached partials if in preview mode We don't want unpublished items in cached views - possible leak to live site. Show previews using normal partial view instead. --- src/Umbraco.Web/CacheHelperExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index ae8df6341568..e3b96106db6a 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -37,7 +37,8 @@ public static IHtmlString CachedPartialView( ViewDataDictionary viewData = null) { //disable cached partials in debug mode: http://issues.umbraco.org/issue/U4-5940 - if (htmlHelper.ViewContext.HttpContext.IsDebuggingEnabled) + //disable cached partials in preview mode: https://github.com/umbraco/Umbraco-CMS/issues/10384 + if (htmlHelper.ViewContext.HttpContext.IsDebuggingEnabled || Umbraco.Web.Composing.Current.UmbracoContext.InPreviewMode) { // just return a normal partial view instead return htmlHelper.Partial(partialViewName, model, viewData); From 7afcd590374e18c7232f38d913c1ee9d51177992 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 1 Jul 2021 13:48:56 +0100 Subject: [PATCH 09/68] Fixed "user-dialog" dashboard markup The logic in the markup was previously incorrect. I changed the check from `tab.length` to `dashboard.length`. The custom dashboard's label isn't `.caption` and it's per dashboard, not per "property". Lastly, `.path` should be `.view`. Resolves #6417 --- .../src/views/common/overlays/user/user.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 fdd267120098..2de04a01471d 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 @@ -130,13 +130,11 @@
-
+
+
{{tab.label}}
-
-

{{property.caption}}

-
-
+
From d6a27d6646de5454e6480a1f7d1e22a63034a650 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Thu, 1 Jul 2021 23:58:59 +0200 Subject: [PATCH 10/68] Color picker: Fix label view (#10445) * Add missing focus styling * Change width to ensure label does not get too wide Co-authored-by: BatJan Co-authored-by: Jan Skovgaard Olsen --- .../src/less/components/umb-color-swatches.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8cf64e183c30..bdfc55f648d2 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; From e67d59a789995fc95cb0f0a30233c9bf0c0899e2 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 22 Mar 2021 18:15:39 +0100 Subject: [PATCH 11/68] Adjust showLabels to be parsed as boolean --- .../src/views/propertyeditors/boolean/boolean.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js index 018c2b72c123..c93b558a23a4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.controller.js @@ -11,8 +11,8 @@ function booleanEditorController($scope) { showLabels: false }; - if ($scope.model.config && $scope.model.config.showLabels && Object.toBoolean($scope.model.config.showLabels)) { - config.showLabels = true; + if ($scope.model.config) { + $scope.model.config.showLabels = $scope.model.config.showLabels ? Object.toBoolean($scope.model.config.showLabels) : config.showLabels; } // Map the user config From 3e208904356f1fe1262a7edee052dbf9663a6ad3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 2 Jul 2021 11:47:39 +0200 Subject: [PATCH 12/68] Fix dependencies to make 8.15 installable via NuGet --- build/NuSpecs/UmbracoCms.Web.nuspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index e96a217f4ecb..7aebfae10870 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -43,7 +43,8 @@ - + + From 1e54d0551438b7ec97072b5fb14551163d17a81f Mon Sep 17 00:00:00 2001 From: Henk Jan Pluim Date: Tue, 6 Jul 2021 16:46:07 +0200 Subject: [PATCH 13/68] #10577 bugfix - Umbraco backoffice allows you to assign the same 'login' on two different Members (#10591) --- src/Umbraco.Core/Services/Implement/MemberService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 877bb5c9ce8a..b26a6bef3915 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -626,7 +626,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pag 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)); From a1e0af6fff971af3270b6292b0be38a6988d3a70 Mon Sep 17 00:00:00 2001 From: Rachel Breeze Date: Tue, 6 Jul 2021 18:39:56 +0100 Subject: [PATCH 14/68] Accessibility improvements for Extensions in Razor Markup (#9576) * Support for language of parts * Changed casing of "FallBack" to "Fallback" to be consistent with the existing code. * Now tests the type to determine if span should be added. * Remove magic strings and return
for IHtmlString values This fixes the "string" vs "String" issue in the previous commit and prevents the function returning invalid markup * Fix for test providing string as "object" Co-authored-by: Joe Glombek --- .../Models/PublishedContent/Fallback.cs | 6 ++ .../PublishedContentLanguageVariantTests.cs | 8 ++- .../PublishedValueFallback.cs | 65 ++++++++++++++++--- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs index 04342185558a..805f14d21b51 100644 --- a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -71,5 +71,11 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + public const int DisplayFallbackLanguage = 4; + /// + /// Gets the fallback to tree ancestors policy. + /// + public static Fallback ToDisplayFallbackLanguage => new Fallback(new[] { DisplayFallbackLanguage }); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 636f8502ed4e..05cf8d01c1d7 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -235,7 +235,13 @@ public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Ove var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } - + [Test] + public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_And_Language_Change() + { + var content = Current.UmbracoContext.Content.GetAtRoot().First(); + var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.DisplayFallbackLanguage)); + Assert.AreEqual("Welcome", value); + } [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 57c3094eaf61..b5901fef690f 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -35,6 +37,8 @@ public bool TryGetValue(IPublishedProperty property, string culture, string s { _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); + var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); + foreach (var f in fallback) { switch (f) @@ -45,9 +49,11 @@ public bool TryGetValue(IPublishedProperty property, string culture, string s value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) + if (TryGetValueWithLanguageFallback(property, culture, segment, out value, includeFallbackLanguage)) return true; break; + case Fallback.DisplayFallbackLanguage: + continue; default: throw NotSupportedFallbackMethod(f, "property"); } @@ -67,6 +73,7 @@ public bool TryGetValue(IPublishedElement content, string alias, string culture, public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) { var propertyType = content.ContentType.GetPropertyType(alias); + var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); if (propertyType == null) { value = default; @@ -85,9 +92,11 @@ public bool TryGetValue(IPublishedElement content, string alias, string cultu value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) return true; break; + case Fallback.DisplayFallbackLanguage: + continue; default: throw NotSupportedFallbackMethod(f, "element"); } @@ -107,6 +116,7 @@ public bool TryGetValue(IPublishedContent content, string alias, string culture, public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty) { noValueProperty = default; + var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) @@ -131,13 +141,15 @@ public virtual bool TryGetValue(IPublishedContent content, string alias, stri case Fallback.Language: if (propertyType == null) continue; - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) return true; break; case Fallback.Ancestors: - if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty, includeFallbackLanguage)) return true; break; + case Fallback.DisplayFallbackLanguage: + continue; default: throw NotSupportedFallbackMethod(f, "content"); } @@ -155,9 +167,10 @@ private NotSupportedException NotSupportedFallbackMethod(int fallback, string le // tries to get a value, recursing the tree // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) - private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty) + private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty, bool includeFallbackLanguage) { IPublishedProperty property; // if we are here, content's property has no value + var originalCulture = culture; do { content = content.Parent; @@ -183,6 +196,10 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent content, stri if (property != null && property.HasValue(culture, segment)) { value = property.Value(culture, segment); + if (includeFallbackLanguage && originalCulture != culture) + { + value = GetMarkUpForFallbackLanguage(culture, value); + } return true; } @@ -191,7 +208,7 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent content, stri } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value, bool includeFallbackLanguage) { value = default; @@ -217,6 +234,10 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str if (property.HasValue(culture2, segment)) { value = property.Value(culture2, segment); + if (includeFallbackLanguage && culture2 != culture) + { + value = GetMarkUpForFallbackLanguage(culture2, value); + } return true; } @@ -225,7 +246,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) { value = default; @@ -251,6 +272,10 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); + if (includeFallbackLanguage && culture2 != culture) + { + value = GetMarkUpForFallbackLanguage(culture2, value); + } return true; } @@ -259,7 +284,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) { value = default; @@ -288,11 +313,35 @@ private bool TryGetValueWithLanguageFallback(IPublishedContent content, strin if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); + if (includeFallbackLanguage && culture2 != culture) + { + value = GetMarkUpForFallbackLanguage(culture2, value); + + } return true; } language = language2; } } + + private T GetMarkUpForFallbackLanguage(string culture2, T value) + { + var typeOfT = typeof(T); + if (value is string) + { + var newValue = "" + value + ""; + return (T)Convert.ChangeType(newValue, typeof(T)); + } + else if (typeOfT == typeof(IHtmlString)) + { + // we want to return a block element here since the IHtmlString could contain futher block elements + var newValue = "
" + value + "
"; + IHtmlString htmlString = new MvcHtmlString(newValue); + return (T)htmlString; + } + + return value; + } } } From 87a7e84cbabdf5f692e52f2d73c6b474092bfaab Mon Sep 17 00:00:00 2001 From: Jan Skovgaard <1932158+BatJan@users.noreply.github.com> Date: Tue, 13 Jul 2021 09:57:20 +0200 Subject: [PATCH 15/68] Bugfix: #10414 - Validation message doesn't disappear once the issue is fixed (#10581) * Add missing focus styling * Fix issue where validation message does not disappear when all chars are removed Co-authored-by: BatJan Co-authored-by: Jan Skovgaard Olsen --- .../src/views/propertyeditors/textbox/textbox.controller.js | 4 ++++ .../src/views/propertyeditors/textbox/textbox.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index b7c740e74940..3e4539c6ae89 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -31,6 +31,10 @@ function textboxController($scope, validationMessageService) { checkLengthVadility(); $scope.nearMaxLimit = $scope.validLength && $scope.charsCount > Math.max($scope.maxChars*.8, $scope.maxChars-25); } + else { + $scope.charsCount = 0; + checkLengthVadility(); + } } $scope.model.onValueChanged = $scope.change; $scope.change(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 5e135ea7d924..1f1131c43f3e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -10,7 +10,7 @@ aria-required="{{model.validation.mandatory}}" aria-invalid="False" ng-trim="false" - ng-keyup="change()" /> + ng-change="change()" />

{{model.label}} {{textboxFieldForm.textbox.errorMsg}}

From c812dab96739c5cfc228c70796af0c2b5b001fec Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 23 Jul 2021 01:26:37 +0200 Subject: [PATCH 16/68] File upload checkered background (#10621) * Use umb-icon component * Cleanup unused css and use checkered background on both image cropper and file upload --- .../components/umb-property-file-upload.less | 2 +- .../src/less/property-editors.less | 321 +++++++++--------- .../upload/umb-property-file-upload.html | 2 +- 3 files changed, 156 insertions(+), 169 deletions(-) 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 83774e2dae77..75d171dd87e4 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 11d11c7e3a37..87603671dd3a 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-cropper-imageholder { - float: left; - } - - .umb-cropper-imageholder umb-image-gravity { - display:block; +.umb-crop-thumbnail-container { + img { + max-width: unset; } - */ +} - .umb-crop-thumbnail-container { - img { - max-width: unset; - } - } +.cropList { + display: inline-block; + position: relative; + vertical-align: top; + flex:0; +} - .cropList { - display: inline-block; - position: relative; - vertical-align: top; - flex:0; - } +.umb-fileupload, +.umb-cropper-gravity { - .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; - - 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; + 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; - 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; - } - + @media (max-width: 768px) { + flex-direction: column; } + +} - .imagecropper .umb-cropper__container { - position: relative; - width: 100%; - } +.imagecropper .umb-cropper__container { + position: relative; + width: 100%; +} - .imagecropper .umb-cropper__container .button-drawer { - display: flex; - justify-content: flex-end; - padding: 10px; - position: relative; +.imagecropper .umb-cropper__container .button-drawer { + display: flex; + justify-content: flex-end; + padding: 10px; + position: relative; - button { - margin-left: 4px; - } + button { + margin-left: 4px; } +} - .umb-close-cropper { - position: absolute; - top: 3px; - right: 3px; - cursor: pointer; - z-index: @zindexCropperOverlay + 1; - } +.umb-close-cropper { + position: absolute; + top: 3px; + right: 3px; + cursor: pointer; + z-index: @zindexCropperOverlay + 1; +} - .umb-close-cropper:hover { - opacity: .9; - background: @gray-10; - } +.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 { + 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 { + 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.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: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, +.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-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-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; - } +.imagecropper .umb-sortable-thumbnails li .crop-annotation { + color: @gray-6; +} - .btn-crop-delete { - display: block; - text-align: left; - } +.btn-crop-delete { + display: block; + text-align: left; +} - .imagecropper .cropList-container { - h5 { - margin-left: 10px; - margin-top: 0; - } +.imagecropper .cropList-container { + h5 { + margin-left: 10px; + margin-top: 0; } +} // // Folder browser 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 fadc0ac3b1d5..1db179aa42ba 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

From 6116ec814d930d59d1db7d5e1c87a70b0cef871e Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Tue, 20 Jul 2021 17:29:01 +0200 Subject: [PATCH 17/68] Update umbeditorview.directive.js --- .../directives/components/editor/umbeditorview.directive.js | 2 ++ 1 file changed, 2 insertions(+) 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 34cb55a794fc..35981049afac 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() { From c86d3b1dae6ea34cc60321c10b39e063daf305fe Mon Sep 17 00:00:00 2001 From: Patrick de Mooij Date: Fri, 16 Oct 2020 12:00:39 +0200 Subject: [PATCH 18/68] 9157: Use the sorting event instead of the saving --- src/Umbraco.Core/Services/Implement/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index af14d7fa699d..7e694946efd6 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2388,7 +2388,7 @@ private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventM if (raiseEvents) { //raise cancelable sorting event - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + if (scope.Events.DispatchCancelable(Sorting, this, saveEventArgs, nameof(Sorting))) return OperationResult.Cancel(evtMsgs); //raise saving event (this one cannot be canceled) From 2eb554f1b46d50ae8445dcdedc2d759fcb9178da Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 14:52:01 +0200 Subject: [PATCH 19/68] Inject windowResizeListener in Image Crop directive (#10745) --- .../components/imaging/umbimagecrop.directive.js | 12 ++++++------ .../common/services/windowresizelistener.service.js | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js index 60ba71d7a52f..1f5d506bdd99 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/imaging/umbimagecrop.directive.js @@ -6,7 +6,7 @@ **/ angular.module("umbraco.directives") .directive('umbImageCrop', - function ($timeout, $window, cropperHelper) { + function ($timeout, cropperHelper, windowResizeListener) { const MAX_SCALE = 4; @@ -26,7 +26,7 @@ angular.module("umbraco.directives") forceUpdate: '@?' }, - link: function (scope, element, attrs, windowResizeListener) { + link: function (scope, element, attrs) { var unsubscribe = []; let sliderRef = null; @@ -72,7 +72,7 @@ angular.module("umbraco.directives") }; function updateSlider() { - if(sliderRef) { + if (sliderRef) { // Update slider range min/max sliderRef.noUiSlider.updateOptions({ "range": { @@ -102,7 +102,7 @@ angular.module("umbraco.directives") // cross-browser wheel delta var delta = Math.max(-50, Math.min(50, (event.wheelDelta || -event.detail))); - if(sliderRef) { + if (sliderRef) { var currentScale =sliderRef.noUiSlider.get(); var newScale = Math.min(Math.max(currentScale + delta*.001*scope.dimensions.image.ratio, scope.dimensions.scale.min), scope.dimensions.scale.max); @@ -127,8 +127,8 @@ angular.module("umbraco.directives") 'left': (parseInt(scope.dimensions.margin.left, 10)) + 'px' } }; - updateStyles(); + updateStyles(); //elements var $viewport = element.find(".viewport"); @@ -138,11 +138,11 @@ angular.module("umbraco.directives") $overlay.bind("focus", function () { $overlay.bind("DOMMouseScroll mousewheel onmousewheel", onScroll); }); + $overlay.bind("blur", function () { $overlay.unbind("DOMMouseScroll mousewheel onmousewheel", onScroll); }); - //default constraints for drag n drop var constraints = { left: { max: 0, min: 0 }, top: { max: 0, min: 0 } }; scope.constraints = constraints; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js index 68691b8629c2..3c13228a9b8f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/windowresizelistener.service.js @@ -11,7 +11,7 @@ */ function windowResizeListener($rootScope) { - var WinReszier = (function () { + var WinResizer = (function () { var registered = []; var inited = false; var resize = _.debounce(function(ev) { @@ -51,7 +51,7 @@ function windowResizeListener($rootScope) { * @param {Function} cb */ register: function (cb) { - WinReszier.register(cb); + WinResizer.register(cb); }, /** @@ -59,9 +59,9 @@ function windowResizeListener($rootScope) { * @param {Function} cb */ unregister: function(cb) { - WinReszier.unregister(cb); + WinResizer.unregister(cb); } }; } -angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); \ No newline at end of file +angular.module('umbraco.services').factory('windowResizeListener', windowResizeListener); From 820952561127bb79910928652cf59ee15ea6fbde Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 15:49:16 +0200 Subject: [PATCH 20/68] Modernize mini listview component to use svg icons and `umb-search-filter` component (#10740) --- .../src/less/components/umb-mini-search.less | 13 ++++----- .../views/components/umb-mini-list-view.html | 28 +++++++++---------- .../src/views/components/umb-mini-search.html | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) 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 ec598c17ebcd..e4eb8f823585 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 @@ -4,13 +4,12 @@ .icon { position: absolute; - width: 30px; - height: 30px; display: flex; justify-content: center; align-items: center; - margin: 1px; - padding: 0; + font-size: 20px; + margin: 5px; + padding: 1px; pointer-events: none; color: @ui-action-discreet-type; transition: color .1s linear; @@ -30,6 +29,7 @@ .icon { color: @ui-action-discreet-type-hover; } + input { color: @ui-action-discreet-border-hover; border-color: @ui-action-discreet-border-hover; @@ -42,10 +42,9 @@ border-color: @ui-action-discreet-border-hover; cursor: unset; } - + input:focus, &:focus-within input, &.--has-value input { width: 190px; - padding-left:30px; + padding-left: 30px; } - } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html index e2319f099d66..dfd6f9002ced 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-mini-list-view.html @@ -5,14 +5,14 @@ ng-repeat="miniListView in miniListViews">
    - +

    {{ miniListView.node.name }}

    @@ -32,19 +32,17 @@

    {{ miniListView.node.name }}

    - 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 3c95dbae5b47..b69071d4e49c 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,5 +1,5 @@ - + Date: Wed, 28 Jul 2021 22:25:19 +0200 Subject: [PATCH 21/68] Don't squeeze avatar when help panel is open --- .../src/less/components/application/umb-app-header.less | 1 + 1 file changed, 1 insertion(+) 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 bd1b8ab07ae0..68a29df89ee3 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, From cb06442de44d93b6437fcf4fa92bb4d12dc68f5e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 29 Jul 2021 13:54:05 +0200 Subject: [PATCH 22/68] Fix: Issue when upgraded from 8.14 to 8.15 when inviting user into the backoffice. #10746 (#10767) * Remove unusable method * Fixes #10746 by removing the recursive call ending in an infinite loop Also: the culture is already known, don't take the `CurrentUICulture` --- .../LocalizedTextServiceExtensions.cs | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 2911441578fe..e062f1130620 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -71,30 +71,6 @@ public static string Localize(this ILocalizedTextService manager, string area, s #pragma warning restore CS0618 // Type or member is obsolete } - /// - /// Localize using the current thread culture - /// - /// - /// - /// - /// - /// - public static string Localize(this ILocalizedTextService manager, string area, string alias, IDictionary tokens = null) - { - if (manager is ILocalizedTextService2 manager2) - { - return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens); - } - var fullKey = alias; - if (area != null) - { - fullKey = string.Concat(area, "/", alias); - } -#pragma warning disable CS0618 // Type or member is obsolete - return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture, tokens); -#pragma warning restore CS0618 // Type or member is obsolete - } - /// /// Localize a key without any variables /// @@ -108,7 +84,7 @@ public static string Localize(this ILocalizedTextService manager, string area, s { if (manager is ILocalizedTextService2 manager2) { - return manager2.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, tokens); + return manager2.Localize(area, alias, culture, ConvertToDictionaryVars(tokens)); } var fullKey = alias; if (area != null) From 168abbf2f7597a8c5ccadded19ef619cb9f82e46 Mon Sep 17 00:00:00 2001 From: Robert Ghafoor Date: Fri, 30 Jul 2021 09:34:14 +0200 Subject: [PATCH 23/68] 10758 Caption will now be correctly set when passing in a target to the media picker. --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 0b9c59f2da62..405556e2ae9c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -168,6 +168,7 @@ angular.module("umbraco") var originalTarget = $scope.target; var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; + var caption = $scope.target.caption; // ID of a UDI or legacy int ID still could be null/undefinied here // As user may dragged in an image that has not been saved to media section yet @@ -181,6 +182,7 @@ angular.module("umbraco") $scope.target.url = mediaHelper.resolveFileFromEntity(node); $scope.target.thumbnail = mediaHelper.resolveFileFromEntity(node, true); $scope.target.altText = altText; + $scope.target.caption = caption; $scope.target.focalPoint = originalTarget.focalPoint; $scope.target.coordinates = originalTarget.coordinates; openDetailsDialog(); From 56790f72b020eaed5f26fd981a4f1f3feb8a4927 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 2 Aug 2021 00:18:30 +0200 Subject: [PATCH 24/68] Replace angular.forEach with Utilities.forEach (#10759) * Replace angular.forEach with Utilities.forEach * Use localizeMany to localize translations in a single request * Replace angular.forEach * Replace angular.forEach in mocks --- .../components/buttons/umbbutton.directive.js | 30 ++++++------ .../content/umbcontentnodeinfo.directive.js | 14 +++--- .../editor/umbeditornavigation.directive.js | 20 ++++---- .../media/umbmedianodeinfo.directive.js | 21 ++++---- .../components/umbaceeditor.directive.js | 2 +- .../components/umbchildselector.directive.js | 32 ++++++------- .../components/umbgridselector.directive.js | 6 +-- .../components/umbgroupsbuilder.directive.js | 48 +++++++++---------- .../components/umbminilistview.directive.js | 10 ++-- .../components/umbnestedcontent.directive.js | 9 ++-- .../upload/umbfiledropzone.directive.js | 5 +- .../util/disabletabindex.directive.js | 2 +- .../util/umbkeyboardlist.directive.js | 2 +- .../src/common/services/retryqueue.service.js | 2 +- .../dashboard/dashboard.tabs.controller.js | 36 +++++++------- .../test/lib/angular/angular-mocks.js | 32 ++++++------- 16 files changed, 131 insertions(+), 140 deletions(-) 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 dd6fa5c5c867..d1be694fde50 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 c20c2a368d0e..f087f6e0841a 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 eec8455969ef..a912eab60963 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/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 6f34cfc0a188..c07777ca60c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -25,13 +25,12 @@ scope.memberOptions.entityType = "MEMBER"; scope.hasMemberReferences = false; - function onInit() { - userService.getCurrentUser().then(function(user){ + userService.getCurrentUser().then(user => { // 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/umbaceeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbaceeditor.directive.js index ef463e6d95bf..f36fbc7ea8e9 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 9a841e3e4ad3..47441326d74a 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 fcc02f53a2a4..bf03749faa7c 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 06e1c61f1e2a..f09d815ae0dd 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 f7b634a71064..b6ba1aaedb6f 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/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index f9b26c81a532..5c8ef162fa34 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/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 79dfee059ee6..d80b884dab57 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 d43282715e85..9381940c746f 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 0b743d0f10fe..3d8653386f60 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 @@ -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 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 16ced1707572..c6aceeb9e016 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/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 534b60c1204a..e497b1ff7f9c 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/test/lib/angular/angular-mocks.js b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js index c9dde30c4fe9..0c9f8f315440 100644 --- a/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js +++ b/src/Umbraco.Web.UI.Client/test/lib/angular/angular-mocks.js @@ -81,7 +81,7 @@ angular.mock.$Browser = function () { self.defer.cancel = function (deferId) { var fnIndex; - angular.forEach(self.deferredFns, function (fn, index) { + Utilities.forEach(self.deferredFns, function (fn, index) { if (fn.id === deferId) fnIndex = index; }); @@ -141,7 +141,7 @@ angular.mock.$Browser.prototype = { * run all fns in pollFns */ poll: function poll() { - angular.forEach(this.pollFns, function (pollFn) { + Utilities.forEach(this.pollFns, function (pollFn) { pollFn(); }); }, @@ -388,9 +388,9 @@ angular.mock.$LogProvider = function () { */ $log.assertEmpty = function () { var errors = []; - angular.forEach(['error', 'warn', 'info', 'log'], function (logLevel) { - angular.forEach($log[logLevel].logs, function (log) { - angular.forEach(log, function (logItem) { + Utilities.forEach(['error', 'warn', 'info', 'log'], function (logLevel) { + Utilities.forEach($log[logLevel].logs, function (log) { + Utilities.forEach(log, function (logItem) { errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); }); }); @@ -598,7 +598,7 @@ angular.mock.$LogProvider = function () { 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - angular.forEach(unimplementedMethods, function (methodName) { + Utilities.forEach(unimplementedMethods, function (methodName) { self[methodName] = function () { throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); }; @@ -688,13 +688,13 @@ angular.mock.dump = function (object) { if (angular.isElement(object)) { object = $(object); out = $('
    '); - angular.forEach(object, function (element) { + Utilities.forEach(object, function (element) { out.append($(element).clone()); }); out = out.html(); } else if (Utilities.isArray(object)) { out = []; - angular.forEach(object, function (o) { + Utilities.forEach(object, function (o) { out.push(serialize(o)); }); out = '[ ' + out.join(', ') + ' ]'; @@ -1343,13 +1343,13 @@ function createHttpBackendMock($rootScope, $delegate, $browser) { function createShortMethods(prefix) { - angular.forEach(['GET', 'DELETE', 'JSONP'], function (method) { + Utilities.forEach(['GET', 'DELETE', 'JSONP'], function (method) { $httpBackend[prefix + method] = function (url, headers) { return $httpBackend[prefix](method, url, undefined, headers) } }); - angular.forEach(['PUT', 'POST', 'PATCH'], function (method) { + Utilities.forEach(['PUT', 'POST', 'PATCH'], function (method) { $httpBackend[prefix + method] = function (url, data, headers) { return $httpBackend[prefix](method, url, data, headers) } @@ -1425,7 +1425,7 @@ function MockXhr() { if (header) return header; header = undefined; - angular.forEach(this.$$respHeaders, function (headerVal, headerName) { + Utilities.forEach(this.$$respHeaders, function (headerVal, headerName) { if (!header && headerName.toLowerCase() == name) header = headerVal; }); return header; @@ -1434,7 +1434,7 @@ function MockXhr() { this.getAllResponseHeaders = function () { var lines = []; - angular.forEach(this.$$respHeaders, function (value, key) { + Utilities.forEach(this.$$respHeaders, function (value, key) { lines.push(key + ': ' + value); }); return lines.join('\n'); @@ -1723,7 +1723,7 @@ window.jstestdriver && (function (window) { */ window.dump = function () { var args = []; - angular.forEach(arguments, function (arg) { + Utilities.forEach(arguments, function (arg) { args.push(angular.mock.dump(arg)); }); jstestdriver.console.log.apply(jstestdriver.console, args); @@ -1757,13 +1757,13 @@ window.jstestdriver && (function (window) { angular.mock.clearDataCache(); // clean up jquery's fragment cache - angular.forEach(angular.element.fragments, function (val, key) { + Utilities.forEach(angular.element.fragments, function (val, key) { delete angular.element.fragments[key]; }); MockXhr.$$lastInstance = null; - angular.forEach(angular.callbacks, function (val, key) { + Utilities.forEach(angular.callbacks, function (val, key) { delete angular.callbacks[key]; }); angular.callbacks.counter = 0; @@ -1798,7 +1798,7 @@ window.jstestdriver && (function (window) { throw Error('Injector already created, can not register a module!'); } else { var modules = currentSpec.$modules || (currentSpec.$modules = []); - angular.forEach(moduleFns, function (module) { + Utilities.forEach(moduleFns, function (module) { modules.push(module); }); } From d9555ea57cfd6e4b8605ca50cd6480a01615d8f8 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 2 Aug 2021 00:31:41 +0200 Subject: [PATCH 25/68] Update noUiSlider to v15.2.0 (#10319) * Upgrade to noUiSlider v15.1.1 * Configurate on drag event * Update nouislider dependencies * Update description * Upgrade to latest noUISlider v15.2.0 --- src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js | 6 +++--- src/Umbraco.Web.UI.Client/package.json | 2 +- .../directives/components/umbrangeslider.directive.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index b5339b60c96b..7c4abcf50c34 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -207,10 +207,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.json b/src/Umbraco.Web.UI.Client/package.json index 45411fad2648..1221728a26e7 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -41,7 +41,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", "signalr": "2.4.0", "spectrum-colorpicker2": "2.0.8", 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 ed74f94f26a5..d5c791281c83 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) { From 050cdd2bb040ed54e8fcb396fa2ba9fd3944baf9 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 2 Aug 2021 00:32:54 +0200 Subject: [PATCH 26/68] Update icon in member group picker (#10094) * Use member group icon as default icon * Make pickers consistent using member group entity icon * Remove unused functions * Use vm * Use original getSelected() function * Include clear function again * Fix merge conflict * Remove comment - remove function is requested from in view * Remove comment - wasn't used, but intend to use in PR 10096 to use property actions to clear member group picker --- .../membergrouppicker.controller.js | 32 +++++++------------ .../membergrouppicker/membergrouppicker.html | 17 +++++----- .../membergroups/membergroups.controller.js | 10 ++++-- .../membergroups/membergroups.html | 8 ++--- .../Models/Mapping/MemberMapDefinition.cs | 1 + 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js index 5362cb1f1006..e64633ea31e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.controller.js @@ -2,6 +2,12 @@ //with a specified callback, this callback will receive an object with a selection on it function memberGroupPicker($scope, editorService, memberGroupResource){ + var vm = this; + + vm.openMemberGroupPicker = openMemberGroupPicker; + vm.remove = remove; + vm.clear = clear; + function trim(str, chr) { var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); return str.replace(rgxtrim, ''); @@ -24,7 +30,7 @@ function memberGroupPicker($scope, editorService, memberGroupResource){ } } - $scope.openMemberGroupPicker = function() { + function openMemberGroupPicker() { var memberGroupPicker = { multiPicker: true, submit: function (model) { @@ -52,31 +58,17 @@ function memberGroupPicker($scope, editorService, memberGroupResource){ } }; editorService.memberGroupPicker(memberGroupPicker); - }; + } - // TODO: I don't believe this is used - $scope.remove = function(index){ + function remove(index) { $scope.renderModel.splice(index, 1); setDirty(); - }; - - // TODO: I don't believe this is used - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return i.id; - }); - - if (currIds.indexOf(item) < 0) { - $scope.renderModel.push({ name: item, id: item, icon: 'icon-users' }); - setDirty(); - } - }; + } - // TODO: I don't believe this is used - $scope.clear = function() { + function clear() { $scope.renderModel = []; setDirty(); - }; + } function renderModelIds() { return _.map($scope.renderModel, function (i) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html index 5a0788149e38..e644fac29d0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergrouppicker/membergrouppicker.html @@ -1,17 +1,18 @@ -
    +
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js index 2213841ece86..af22f6c8004f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.controller.js @@ -1,4 +1,10 @@ function memberGroupController($scope, editorService, memberGroupResource) { + + var vm = this; + + vm.pickGroup = pickGroup; + vm.removeGroup = removeGroup; + //set the selected to the keys of the dictionary who's value is true $scope.getSelected = function () { var selected = []; @@ -16,7 +22,7 @@ } } - $scope.pickGroup = function() { + function pickGroup() { editorService.memberGroupPicker({ multiPicker: true, submit: function (model) { @@ -39,7 +45,7 @@ }); } - $scope.removeGroup = function (group) { + function removeGroup(group) { $scope.model.value[group] = false; setDirty(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html index abfa628e90cb..81b9f9d30b35 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/membergroups/membergroups.html @@ -1,15 +1,15 @@ -
    +
    + on-remove="vm.removeGroup(group)"> diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index f86e7eb1fca3..dcbe6b753425 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -143,6 +143,7 @@ private void Map(MembershipUser source, MemberBasic target, MapperContext contex // 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; From d537545fa9f4c9899afe681778c2080bb6852864 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 20:05:47 +0200 Subject: [PATCH 27/68] Use umb-icon component in user history --- .../src/views/common/overlays/user/user.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2de04a01471d..e284a10556e7 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 @@
    -
    From d40e503c824939d69842580e24021438f4d85132 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 2 Aug 2021 01:26:30 +0200 Subject: [PATCH 28/68] V8: Fix JS error when using the hide search function in treepicker (#6931) * Fix JS error when using the hide search function in treepicker * updates with same defensive change from original pr - only explores child.children after confirming the child indeed has children Co-authored-by: Nathan Woulfe --- .../common/infiniteeditors/treepicker/treepicker.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 0c5fe9af1bf6..ed5c4096bcca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -574,6 +574,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", var listViewResults = vm.searchInfo.selectedSearchResults.filter(i => i.parentId === child.id); listViewResults.forEach(item => { + if (!child.children) return; + var childExists = child.children.find(c => c.id === item.id); if (!childExists) { From 96d593593381c48f57b0201a686957c1a37bddbb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 4 Aug 2021 14:19:34 +0200 Subject: [PATCH 29/68] Revert "Accessibility improvements for Extensions in Razor Markup (#9576)" This reverts commit a1e0af6fff971af3270b6292b0be38a6988d3a70. --- .../Models/PublishedContent/Fallback.cs | 6 -- .../PublishedContentLanguageVariantTests.cs | 8 +-- .../PublishedValueFallback.cs | 65 +++---------------- 3 files changed, 9 insertions(+), 70 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs index 805f14d21b51..04342185558a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -71,11 +71,5 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } - - public const int DisplayFallbackLanguage = 4; - /// - /// Gets the fallback to tree ancestors policy. - /// - public static Fallback ToDisplayFallbackLanguage => new Fallback(new[] { DisplayFallbackLanguage }); } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 05cf8d01c1d7..636f8502ed4e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -235,13 +235,7 @@ public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Ove var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)); Assert.AreEqual("Welcome", value); } - [Test] - public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_And_Language_Change() - { - var content = Current.UmbracoContext.Content.GetAtRoot().First(); - var value = content.Value("welcomeText", "it", fallback: Fallback.To(Fallback.Language, Fallback.DisplayFallbackLanguage)); - Assert.AreEqual("Welcome", value); - } + [Test] public void Do_Not_GetContent_For_Unpopulated_Requested_Language_With_Fallback_Over_That_Loops() { diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index b5901fef690f..57c3094eaf61 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Web; -using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -37,8 +35,6 @@ public bool TryGetValue(IPublishedProperty property, string culture, string s { _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); - var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); - foreach (var f in fallback) { switch (f) @@ -49,11 +45,9 @@ public bool TryGetValue(IPublishedProperty property, string culture, string s value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(property, culture, segment, out value, includeFallbackLanguage)) + if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) return true; break; - case Fallback.DisplayFallbackLanguage: - continue; default: throw NotSupportedFallbackMethod(f, "property"); } @@ -73,7 +67,6 @@ public bool TryGetValue(IPublishedElement content, string alias, string culture, public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) { var propertyType = content.ContentType.GetPropertyType(alias); - var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); if (propertyType == null) { value = default; @@ -92,11 +85,9 @@ public bool TryGetValue(IPublishedElement content, string alias, string cultu value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) return true; break; - case Fallback.DisplayFallbackLanguage: - continue; default: throw NotSupportedFallbackMethod(f, "element"); } @@ -116,7 +107,6 @@ public bool TryGetValue(IPublishedContent content, string alias, string culture, public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty) { noValueProperty = default; - var includeFallbackLanguage = fallback.Contains(Fallback.DisplayFallbackLanguage); var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) @@ -141,15 +131,13 @@ public virtual bool TryGetValue(IPublishedContent content, string alias, stri case Fallback.Language: if (propertyType == null) continue; - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value, includeFallbackLanguage)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) return true; break; case Fallback.Ancestors: - if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty, includeFallbackLanguage)) + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) return true; break; - case Fallback.DisplayFallbackLanguage: - continue; default: throw NotSupportedFallbackMethod(f, "content"); } @@ -167,10 +155,9 @@ private NotSupportedException NotSupportedFallbackMethod(int fallback, string le // tries to get a value, recursing the tree // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) - private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty, bool includeFallbackLanguage) + private bool TryGetValueWithAncestorsFallback(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty) { IPublishedProperty property; // if we are here, content's property has no value - var originalCulture = culture; do { content = content.Parent; @@ -196,10 +183,6 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent content, stri if (property != null && property.HasValue(culture, segment)) { value = property.Value(culture, segment); - if (includeFallbackLanguage && originalCulture != culture) - { - value = GetMarkUpForFallbackLanguage(culture, value); - } return true; } @@ -208,7 +191,7 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent content, stri } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value, bool includeFallbackLanguage) + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value) { value = default; @@ -234,10 +217,6 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str if (property.HasValue(culture2, segment)) { value = property.Value(culture2, segment); - if (includeFallbackLanguage && culture2 != culture) - { - value = GetMarkUpForFallbackLanguage(culture2, value); - } return true; } @@ -246,7 +225,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value) { value = default; @@ -272,10 +251,6 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); - if (includeFallbackLanguage && culture2 != culture) - { - value = GetMarkUpForFallbackLanguage(culture2, value); - } return true; } @@ -284,7 +259,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value, bool includeFallbackLanguage) + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value) { value = default; @@ -313,35 +288,11 @@ private bool TryGetValueWithLanguageFallback(IPublishedContent content, strin if (content.HasValue(alias, culture2, segment)) { value = content.Value(alias, culture2, segment); - if (includeFallbackLanguage && culture2 != culture) - { - value = GetMarkUpForFallbackLanguage(culture2, value); - - } return true; } language = language2; } } - - private T GetMarkUpForFallbackLanguage(string culture2, T value) - { - var typeOfT = typeof(T); - if (value is string) - { - var newValue = "" + value + ""; - return (T)Convert.ChangeType(newValue, typeof(T)); - } - else if (typeOfT == typeof(IHtmlString)) - { - // we want to return a block element here since the IHtmlString could contain futher block elements - var newValue = "
    " + value + "
    "; - IHtmlString htmlString = new MvcHtmlString(newValue); - return (T)htmlString; - } - - return value; - } } } From 14fc72ff2bd85538cc36bab7f013d3738a97d06f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Aug 2021 09:50:58 +0200 Subject: [PATCH 30/68] Reverting ConcurrentHashSet for now so that Umbraco can install again, see #10751 --- .../Collections/ConcurrentHashSet.cs | 109 +++++++++++++++--- src/umbraco.sln.DotSettings | 1 + 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 0424c4dae1e4..c4dba51acd5b 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,8 +1,8 @@ using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Umbraco.Core.Collections { @@ -14,10 +14,9 @@ namespace Umbraco.Core.Collections [Serializable] public class ConcurrentHashSet : ICollection { - // Internally we just use a ConcurrentDictionary with the same value for the Value (since we don't actually care about the value) - private static readonly byte _emptyValue = 0x0; - private readonly ConcurrentDictionary _innerSet = new ConcurrentDictionary(); - + private readonly HashSet _innerSet = new HashSet(); + private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + /// /// Returns an enumerator that iterates through the collection. /// @@ -27,7 +26,7 @@ public class ConcurrentHashSet : ICollection /// 1 public IEnumerator GetEnumerator() { - return _innerSet.Keys.GetEnumerator(); + return GetThreadSafeClone().GetEnumerator(); } /// @@ -51,7 +50,16 @@ IEnumerator IEnumerable.GetEnumerator() /// The object to remove from the .The is read-only. public bool Remove(T item) { - return _innerSet.TryRemove(item, out _); + try + { + _instanceLocker.EnterWriteLock(); + return _innerSet.Remove(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } @@ -66,7 +74,17 @@ public int Count { get { - return _innerSet.Count; + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Count; + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + } } @@ -81,10 +99,19 @@ public int Count /// /// Adds an item to the . /// - /// The object to add to the . + /// The object to add to the .The is read-only. public void Add(T item) { - _innerSet.TryAdd(item, _emptyValue); + try + { + _instanceLocker.EnterWriteLock(); + _innerSet.Add(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -94,7 +121,21 @@ public void Add(T item) /// public bool TryAdd(T item) { - return _innerSet.TryAdd(item, _emptyValue); + if (Contains(item)) return false; + try + { + _instanceLocker.EnterWriteLock(); + + //double check + if (_innerSet.Contains(item)) return false; + _innerSet.Add(item); + return true; + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -103,7 +144,16 @@ public bool TryAdd(T item) /// The is read-only. public void Clear() { - _innerSet.Clear(); + try + { + _instanceLocker.EnterWriteLock(); + _innerSet.Clear(); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) + _instanceLocker.ExitWriteLock(); + } } /// @@ -115,7 +165,16 @@ public void Clear() /// The object to locate in the . public bool Contains(T item) { - return _innerSet.ContainsKey(item); + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Contains(item); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } } /// @@ -124,8 +183,24 @@ public bool Contains(T item) /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . public void CopyTo(T[] array, int index) { - var keys = _innerSet.Keys; - keys.CopyTo(array, index); + var clone = GetThreadSafeClone(); + clone.CopyTo(array, index); + } + + private HashSet GetThreadSafeClone() + { + HashSet clone = null; + try + { + _instanceLocker.EnterReadLock(); + clone = new HashSet(_innerSet, _innerSet.Comparer); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + _instanceLocker.ExitReadLock(); + } + return clone; } /// @@ -134,8 +209,8 @@ public void CopyTo(T[] array, int index) /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 public void CopyTo(Array array, int index) { - var keys = _innerSet.Keys; - Array.Copy(keys.ToArray(), 0, array, index, keys.Count); + var clone = GetThreadSafeClone(); + Array.Copy(clone.ToArray(), 0, array, index, clone.Count); } } } diff --git a/src/umbraco.sln.DotSettings b/src/umbraco.sln.DotSettings index 3d42d188af01..bd8ca8b97436 100644 --- a/src/umbraco.sln.DotSettings +++ b/src/umbraco.sln.DotSettings @@ -1,4 +1,5 @@  + True <data><IncludeFilters /><ExcludeFilters /></data> <data /> Disposable construction From 6a12f4af9033d3ab58e9aa1102449045ba511c7f Mon Sep 17 00:00:00 2001 From: Rowan Bottema Date: Thu, 5 Aug 2021 11:10:58 +0200 Subject: [PATCH 31/68] Update Serilog dependencies This should at least fix #10793 because of a memory leak fix in Serilog.Sinks.Map 1.0.2. Also updated the rest for good measure. Note that Serilog.Sinks.File is not upgraded to the latest version because it is a new major (4.1.0 -> 5.0.0). Also note that Serilog.Filters.Expressions is deprecated and should be replaced by Serilog.Expressions. I considered both out of scope for this fix. --- build/NuSpecs/UmbracoCms.Core.nuspec | 16 ++++++++-------- src/Umbraco.Core/Umbraco.Core.csproj | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index fce15eb487ae..c94def143b5b 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -32,16 +32,16 @@ - + - - - - + + + + - - - + + + diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5dfcd14469fa..146197c4f8f6 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -67,10 +67,10 @@ all - 1.3.0 + 1.5.0 - 1.0.0 + 1.0.2 1.0.5 @@ -94,28 +94,28 @@ - 2.8.0 + 2.10.0 2.0.1 - 3.0.0 + 3.1.0 - 2.0.0 + 2.1.0 - 1.0.0 + 1.1.0 - 1.0.3 + 1.0.5 2.2.2 - 4.0.0 + 4.1.0 From 7800fe83a8978440be9debc6800d1403f8fdfc0b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Aug 2021 11:57:03 +0200 Subject: [PATCH 32/68] Install MessagePack's dependencies explicitly (#10772) --- build/NuSpecs/UmbracoCms.Web.nuspec | 4 ++++ build/NuSpecs/tools/Web.config.install.xdt | 17 ++++++++++++++++- src/Umbraco.Tests/App.config | 14 +++++++++++++- src/Umbraco.Web.UI/web.Template.config | 15 +++++++++++++-- src/Umbraco.Web/Umbraco.Web.csproj | 12 ++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 7aebfae10870..04b9905f9d6b 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -41,7 +41,11 @@ + + + + diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index e215bdbf298a..fe5b7db704bc 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/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 4af04827e36b..c8e8fdac116c 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -76,7 +76,7 @@ - + @@ -98,6 +98,18 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index c6b1eb686c7b..e61c6585ad43 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -236,7 +236,7 @@ - + @@ -258,7 +258,18 @@ - + + + + + + + + + + + + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e830722cc788..316f5def8eb4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -101,6 +101,18 @@ all + + 4.5.1 + + + 4.5.4 + + + 4.5.0 + + + 1.7.1 + 1.0.5 From a7c0edec288c62ce996fd85226831a39e88df33d Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 5 Aug 2021 15:05:03 +0200 Subject: [PATCH 33/68] Support custom SVG icons in user groups list (#10739) --- .../infiniteeditors/usergrouppicker/usergrouppicker.html | 4 ++-- .../views/components/users/umb-user-group-preview.html | 8 ++++---- .../src/views/users/views/groups/groups.html | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) 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 252c1b7cd525..dbddb141ddb7 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/components/users/umb-user-group-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-group-preview.html index 20718cf8046d..db0c6c86c39b 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/users/views/groups/groups.html b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html index 46923dd27d12..106a8c8454d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.html @@ -81,8 +81,8 @@ ng-class="{'-selected': group.selected, '-selectable': group.hasAccess && !group.isSystemUserGroup}">
    - - + +
    Date: Thu, 5 Aug 2021 15:22:23 +0200 Subject: [PATCH 34/68] Tour backdrop missing (#10762) --- .../components/tree/umbtree.directive.js | 16 ++-------------- .../src/common/services/backdrop.service.js | 14 +++++++------- .../src/common/services/navigation.service.js | 18 ++++++++++++------ .../src/common/services/overlay.service.js | 7 ++++++- .../components/application/umb-backdrop.html | 2 +- 5 files changed, 28 insertions(+), 29 deletions(-) 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 7868f798091b..4512494202b9 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.closeBackdrop(); if (n.metaData && n.metaData.noAccess === true) { ev.preventDefault(); 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 4f977cb1b239..e628e48306bc 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 c628e3a5b1d6..bba44a94a00c 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,25 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } function closeBackdrop() { - var aboveClass = 'above-backdrop'; - var leftColumn = $('#leftcolumn'); - var isLeftColumnOnTop = leftColumn.hasClass(aboveClass); - if(isLeftColumnOnTop){ + var tourIsOpen = document.body.classList.contains("umb-tour-is-visible"); + if (tourIsOpen) { + return; + } + + var aboveClass = "above-backdrop"; + var leftColumn = document.getElementById("leftcolumn"); + var isLeftColumnOnTop = leftColumn.classList.contains(aboveClass); + + if (isLeftColumnOnTop) { backdropService.close(); - leftColumn.removeClass(aboveClass); + 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 ea05dad4e72e..8a965f2c78d9 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/views/components/application/umb-backdrop.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-backdrop.html index da1f61ee4a9d..1ff3960ae979 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 @@
    -
    +
    From 4513a85d14d7fda505f8c792d4082ff764314af9 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 27 Jul 2021 20:31:31 +0200 Subject: [PATCH 35/68] Only set umb-button-ellipsis opacity to 1 when above backdrop --- .../less/components/buttons/umb-button-ellipsis.less | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 7104e6478fcc..b6acb2f6cf19 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; From 120bc2af115f904dc07b74fa00fd61ca46218832 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 5 Aug 2021 15:57:14 +0200 Subject: [PATCH 36/68] Examine dashboard search adjustments (#10735) --- .../forms/umbsearchfilter.directive.js | 21 +++++++ .../components/application/umb-search.less | 2 +- .../src/less/utilities/_spacing.less | 8 +++ .../blockpicker/blockpicker.html | 2 +- .../components/forms/umb-search-filter.html | 2 + .../settings/examinemanagement.controller.js | 2 +- .../dashboard/settings/examinemanagement.html | 55 ++++++++++++------- 7 files changed, 68 insertions(+), 24 deletions(-) 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 2e9f15913cb4..5eb22dcecd5d 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: "
    - + +
    @@ -59,7 +61,7 @@
    - +
    @@ -116,15 +118,19 @@
    -