From 7d135899be68a4b79bfada012c2b438461772042 Mon Sep 17 00:00:00 2001 From: nzdev <834725+nzdev@users.noreply.github.com> Date: Sun, 21 Feb 2021 20:45:03 +1300 Subject: [PATCH 01/24] load only once --- .../Sync/DatabaseServerMessenger.cs | 90 ++++++++++++------- .../Sync/ISyncBootStateAccessor.cs | 35 ++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../PublishedContent/NuCacheChildrenTests.cs | 4 +- .../PublishedContent/NuCacheTests.cs | 4 +- .../Scoping/ScopedNuCacheTests.cs | 3 +- .../ContentTypeServiceVariantsTests.cs | 4 +- .../TestHelpers/TestSyncBootStateAccessor.cs | 23 +++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + ...aseServerRegistrarAndMessengerComponent.cs | 1 + .../NuCache/PublishedSnapshotService.cs | 17 +++- 11 files changed, 146 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs create mode 100644 src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs mode change 100755 => 100644 src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 7442169b44..8264ddd79c 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -28,7 +28,7 @@ namespace Umbraco.Core.Sync // but only processes instructions coming from remote servers, // thus ensuring that instructions run only once // - public class DatabaseServerMessenger : ServerMessengerBase + public class DatabaseServerMessenger : ServerMessengerBase, ISyncBootStateAccessor { private readonly IRuntimeState _runtime; private readonly ManualResetEvent _syncIdle; @@ -172,35 +172,7 @@ namespace Umbraco.Core.Sync lock (_locko) { if (_released) return; - - var coldboot = false; - if (_lastId < 0) // never synced before - { - // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new - // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. - Logger.Warn("No last synced Id found, this generally means this is a new server/install." - + " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in" - + " the database and maintain cache updates based on that Id."); - - coldboot = true; - } - else - { - //check for how many instructions there are to process, each row contains a count of the number of instructions contained in each - //row so we will sum these numbers to get the actual count. - var count = database.ExecuteScalar("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); - if (count > Options.MaxProcessingInstructionCount) - { - //too many instructions, proceed to cold boot - Logger.Warn( - "The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})." - + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" - + " to the latest found in the database and maintain cache updates based on that Id.", - count, Options.MaxProcessingInstructionCount); - - coldboot = true; - } - } + var coldboot = IsColdBoot(database); if (coldboot) { @@ -223,6 +195,40 @@ namespace Umbraco.Core.Sync } } + private bool IsColdBoot(IUmbracoDatabase database) + { + var coldboot = false; + if (_lastId < 0) // never synced before + { + // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new + // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. + Logger.Warn("No last synced Id found, this generally means this is a new server/install." + + " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in" + + " the database and maintain cache updates based on that Id."); + + coldboot = true; + } + else + { + //check for how many instructions there are to process, each row contains a count of the number of instructions contained in each + //row so we will sum these numbers to get the actual count. + var count = database.ExecuteScalar("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); + if (count > Options.MaxProcessingInstructionCount) + { + //too many instructions, proceed to cold boot + Logger.Warn( + "The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})." + + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" + + " to the latest found in the database and maintain cache updates based on that Id.", + count, Options.MaxProcessingInstructionCount); + + coldboot = true; + } + } + + return coldboot; + } + /// /// Synchronize the server (throttled). /// @@ -548,6 +554,30 @@ namespace Umbraco.Core.Sync #endregion + public SyncBootState GetSyncBootState() + { + try + { + ReadLastSynced(); // get _lastId + using (var scope = ScopeProvider.CreateScope()) + { + EnsureInstructions(scope.Database); + bool isColdBoot = IsColdBoot(scope.Database); + + if (isColdBoot) + { + return SyncBootState.ColdBoot; + } + return SyncBootState.HasSyncState; + } + } + catch(Exception ex) + { + Logger.Warn("Error determining Sync Boot State", ex); + return SyncBootState.Unknown; + } + } + #region Notify refreshers private static ICacheRefresher GetRefresher(Guid id) diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs new file mode 100644 index 0000000000..a7b7c58235 --- /dev/null +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Sync +{ + /// + /// Retrieve the state of the sync service + /// + public interface ISyncBootStateAccessor + { + /// + /// Get the boot state + /// + /// + SyncBootState GetSyncBootState(); + } + public enum SyncBootState + { + /// + /// Unknown state. Treat as HasSyncState + /// + Unknown = 0, + /// + /// Cold boot. No Sync state + /// + ColdBoot = 1, + /// + /// Warm boot. Sync state present + /// + HasSyncState = 2 + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 465ddee6ee..5c3dd074dd 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -173,6 +173,7 @@ + diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 834d211994..068a161268 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Strings; +using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; @@ -155,7 +156,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, Mock.Of(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 0e05e6baad..652891c476 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Strings; +using Umbraco.Core.Sync; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; using Umbraco.Tests.Testing.Objects.Accessors; @@ -201,7 +202,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, Mock.Of(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index c7c403b260..e204bd7b5f 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -99,7 +99,8 @@ namespace Umbraco.Tests.Scoping Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 9391b7442f..9cd4bb63e8 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; +using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Web.PublishedCache; @@ -70,7 +71,8 @@ namespace Umbraco.Tests.Services Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs b/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs new file mode 100644 index 0000000000..e5f6989381 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Sync; + +namespace Umbraco.Tests.TestHelpers +{ + class TestSyncBootStateAccessor : ISyncBootStateAccessor + { + private readonly SyncBootState _syncBootState; + + public TestSyncBootStateAccessor(SyncBootState syncBootState) + { + _syncBootState = syncBootState; + } + public SyncBootState GetSyncBootState() + { + return _syncBootState; + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 2ac28aa7d7..6c4f7415ea 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -175,6 +175,7 @@ + diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 2fa9d80779..688fc268b0 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -72,6 +72,7 @@ namespace Umbraco.Web.Compose composition.SetDatabaseServerMessengerOptions(GetDefaultOptions); composition.SetServerMessenger(); + composition.Register(Lifetime.Singleton); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs old mode 100755 new mode 100644 index a39e26e2b1..a592fed4be --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -24,6 +24,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Core.Sync; using Umbraco.Web.Cache; using Umbraco.Web.Install; using Umbraco.Web.PublishedCache.NuCache.DataSource; @@ -63,6 +64,8 @@ namespace Umbraco.Web.PublishedCache.NuCache private bool _localContentDbExists; private bool _localMediaDbExists; + private readonly ISyncBootStateAccessor _syncBootStateAccessor; + // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching // means faster execution, but uses memory - not sure if we want it @@ -81,7 +84,8 @@ namespace Umbraco.Web.PublishedCache.NuCache IDataSource dataSource, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, - UrlSegmentProviderCollection urlSegmentProviders) + UrlSegmentProviderCollection urlSegmentProviders, + ISyncBootStateAccessor syncBootStateAccessor) : base(publishedSnapshotAccessor, variationContextAccessor) { //if (Interlocked.Increment(ref _singletonCheck) > 1) @@ -99,6 +103,8 @@ namespace Umbraco.Web.PublishedCache.NuCache _globalSettings = globalSettings; _urlSegmentProviders = urlSegmentProviders; + _syncBootStateAccessor = syncBootStateAccessor; + // we need an Xml serializer here so that the member cache can support XPath, // for members this is done by navigating the serialized-to-xml member _entitySerializer = entitySerializer; @@ -217,7 +223,12 @@ namespace Umbraco.Web.PublishedCache.NuCache { var okContent = false; var okMedia = false; - + if (_syncBootStateAccessor.GetSyncBootState() == SyncBootState.ColdBoot) + { + _logger.Warn("Sync Service is in a Cold Boot state. Skip LoadCachesOnStartup as the Sync Service will trigger a full reload"); + _isReady = true; + return; + } try { if (_localContentDbExists) @@ -233,7 +244,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (!okMedia) _logger.Warn("Loading media from local db raised warnings, will reload from database."); } - + if (!okContent) LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true)); From 5f9d126ab7d087a204e32aed78148b07bc12ea2b Mon Sep 17 00:00:00 2001 From: nzdev <834725+nzdev@users.noreply.github.com> Date: Fri, 26 Mar 2021 16:13:47 +1300 Subject: [PATCH 02/24] fix support for non run states --- .../Sync/ISyncBootStateAccessor.cs | 15 ------------ .../Sync/NonRuntimeLevelBootStateAccessor.cs | 19 +++++++++++++++ src/Umbraco.Core/Sync/SyncBootState.cs | 24 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ ...aseServerRegistrarAndMessengerComponent.cs | 2 +- .../PublishedCache/NuCache/NuCacheComposer.cs | 4 ++++ 6 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs create mode 100644 src/Umbraco.Core/Sync/SyncBootState.cs diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs index a7b7c58235..4b8500f2d9 100644 --- a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -17,19 +17,4 @@ namespace Umbraco.Core.Sync /// SyncBootState GetSyncBootState(); } - public enum SyncBootState - { - /// - /// Unknown state. Treat as HasSyncState - /// - Unknown = 0, - /// - /// Cold boot. No Sync state - /// - ColdBoot = 1, - /// - /// Warm boot. Sync state present - /// - HasSyncState = 2 - } } diff --git a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs new file mode 100644 index 0000000000..70cec6cc96 --- /dev/null +++ b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Sync +{ + /// + /// Boot state implementation for when umbraco is not in the run state + /// + public class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor + { + public SyncBootState GetSyncBootState() + { + return SyncBootState.Unknown; + } + } +} diff --git a/src/Umbraco.Core/Sync/SyncBootState.cs b/src/Umbraco.Core/Sync/SyncBootState.cs new file mode 100644 index 0000000000..4abc53abba --- /dev/null +++ b/src/Umbraco.Core/Sync/SyncBootState.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Sync +{ + public enum SyncBootState + { + /// + /// Unknown state. Treat as HasSyncState + /// + Unknown = 0, + /// + /// Cold boot. No Sync state + /// + ColdBoot = 1, + /// + /// Warm boot. Sync state present + /// + HasSyncState = 2 + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5c3dd074dd..e0c0d78112 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -174,6 +174,8 @@ + + diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 688fc268b0..26ba0db324 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web.Compose composition.SetDatabaseServerMessengerOptions(GetDefaultOptions); composition.SetServerMessenger(); - composition.Register(Lifetime.Singleton); + composition.Register(factory=> factory.GetInstance() as BatchedDatabaseServerMessenger, Lifetime.Singleton); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs index f748fd555c..99f2786d49 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs @@ -1,5 +1,6 @@ using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.Sync; using Umbraco.Web.PublishedCache.NuCache.DataSource; namespace Umbraco.Web.PublishedCache.NuCache @@ -10,6 +11,9 @@ namespace Umbraco.Web.PublishedCache.NuCache { base.Compose(composition); + //Overriden on Run state in DatabaseServerRegistrarAndMessengerComposer + composition.Register(Lifetime.Singleton); + // register the NuCache database data source composition.Register(); From 306c82f871a0346b04322b604ceaf569a3861fbd Mon Sep 17 00:00:00 2001 From: nzdev <834725+nzdev@users.noreply.github.com> Date: Fri, 23 Apr 2021 17:03:32 +1200 Subject: [PATCH 03/24] Change log to Info --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index a592fed4be..58892057d2 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -225,7 +225,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var okMedia = false; if (_syncBootStateAccessor.GetSyncBootState() == SyncBootState.ColdBoot) { - _logger.Warn("Sync Service is in a Cold Boot state. Skip LoadCachesOnStartup as the Sync Service will trigger a full reload"); + _logger.Info("Sync Service is in a Cold Boot state. Skip LoadCachesOnStartup as the Sync Service will trigger a full reload"); _isReady = true; return; } From 053a56a45bc8d6b9917513a84d9953039790f31a Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 26 Apr 2021 18:07:23 +1000 Subject: [PATCH 04/24] Removes the callbacks from DatabaseServerMessenger, moves logic for cold boot into the components responsible for what needs to happen in cold boot. This is still not ideal but we are stuck with backwards compat. In netcore the initialization will be fixed up (if not already). Ensure examine rebuilds don't occur until the first http request is done instead of on a timer which could be problematic with cold boots. --- .../Sync/DatabaseServerMessenger.cs | 143 ++++++++---------- .../Sync/DatabaseServerMessengerOptions.cs | 10 +- .../Sync/ISyncBootStateAccessor.cs | 9 +- .../Sync/NonRuntimeLevelBootStateAccessor.cs | 2 + src/Umbraco.Core/Sync/SyncBootState.cs | 14 +- .../PublishedContent/NuCacheChildrenTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 2 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 2 +- .../TestHelpers/TestSyncBootStateAccessor.cs | 3 + ...aseServerRegistrarAndMessengerComponent.cs | 75 +-------- ...baseServerRegistrarAndMessengerComposer.cs | 38 +++++ .../NuCache/PublishedSnapshotService.cs | 24 +-- .../Search/ExamineFinalComponent.cs | 58 +++++-- src/Umbraco.Web/Umbraco.Web.csproj | 3 +- 15 files changed, 198 insertions(+), 189 deletions(-) create mode 100644 src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComposer.cs diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index ebc77dbdca..2b52a0948e 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -39,9 +39,9 @@ namespace Umbraco.Core.Sync private int _lastId = -1; private DateTime _lastSync; private DateTime _lastPruned; - private bool _initialized; private bool _syncing; private bool _released; + private readonly Lazy _getSyncBootState; public DatabaseServerMessengerOptions Options { get; } @@ -59,6 +59,7 @@ namespace Umbraco.Core.Sync _lastPruned = _lastSync = DateTime.UtcNow; _syncIdle = new ManualResetEvent(true); _distCacheFilePath = new Lazy(() => GetDistCacheFilePath(globalSettings)); + _getSyncBootState = new Lazy(BootInternal); } protected ILogger Logger { get; } @@ -75,7 +76,7 @@ namespace Umbraco.Core.Sync { // we don't care if there's servers listed or not, // if distributed call is enabled we will make the call - return _initialized && DistributedEnabled; + return _getSyncBootState.IsValueCreated && DistributedEnabled; } protected override void DeliverRemote( @@ -118,6 +119,12 @@ namespace Umbraco.Core.Sync /// Callers MUST ensure thread-safety. /// protected void Boot() + { + var bootState = GetSyncBootState(); + Booting?.Invoke(this, bootState); + } + + private SyncBootState BootInternal() { // weight:10, must release *before* the published snapshot service, because once released // the service will *not* be able to properly handle our notifications anymore @@ -139,7 +146,7 @@ namespace Umbraco.Core.Sync // properly releasing MainDom - a timeout here means that one refresher // is taking too much time processing, however when it's done we will // not update lastId and stop everything - var idle =_syncIdle.WaitOne(5000); + var idle = _syncIdle.WaitOne(5000); if (idle == false) { Logger.Warn("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed."); @@ -147,17 +154,23 @@ namespace Umbraco.Core.Sync }, weight); + SyncBootState bootState = SyncBootState.Unknown; + if (registered == false) - return; + { + return bootState; + } ReadLastSynced(); // get _lastId using (var scope = ScopeProvider.CreateScope()) { EnsureInstructions(scope.Database); // reset _lastId if instructions are missing - Initialize(scope.Database); // boot + bootState = Initialize(scope.Database); // boot scope.Complete(); } + + return bootState; } /// @@ -167,36 +180,11 @@ namespace Umbraco.Core.Sync /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// Callers MUST ensure thread-safety. /// - private void Initialize(IUmbracoDatabase database) + private SyncBootState Initialize(IUmbracoDatabase database) { - lock (_locko) - { - if (_released) return; - var coldboot = IsColdBoot(database); + // could occur if shutting down immediately once starting up and before we've initialized + if (_released) return SyncBootState.Unknown; - if (coldboot) - { - // go get the last id in the db and store it - // note: do it BEFORE initializing otherwise some instructions might get lost - // when doing it before, some instructions might run twice - not an issue - var maxId = database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); - - //if there is a max currently, or if we've never synced - if (maxId > 0 || _lastId < 0) - SaveLastSynced(maxId); - - // execute initializing callbacks - if (Options.InitializingCallbacks != null) - foreach (var callback in Options.InitializingCallbacks) - callback(); - } - - _initialized = true; - } - } - - private bool IsColdBoot(IUmbracoDatabase database) - { var coldboot = false; if (_lastId < 0) // never synced before { @@ -206,27 +194,48 @@ namespace Umbraco.Core.Sync + " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in" + " the database and maintain cache updates based on that Id."); - coldboot = true; - } - else + coldboot = true; + } + else + { + //check for how many instructions there are to process, each row contains a count of the number of instructions contained in each + //row so we will sum these numbers to get the actual count. + var count = database.ExecuteScalar("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new { lastId = _lastId }); + if (count > Options.MaxProcessingInstructionCount) { - //check for how many instructions there are to process, each row contains a count of the number of instructions contained in each - //row so we will sum these numbers to get the actual count. - var count = database.ExecuteScalar("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); - if (count > Options.MaxProcessingInstructionCount) - { - //too many instructions, proceed to cold boot - Logger.Warn( - "The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})." - + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" - + " to the latest found in the database and maintain cache updates based on that Id.", - count, Options.MaxProcessingInstructionCount); + //too many instructions, proceed to cold boot + Logger.Warn( + "The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})." + + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" + + " to the latest found in the database and maintain cache updates based on that Id.", + count, Options.MaxProcessingInstructionCount); coldboot = true; } } - return coldboot; + if (coldboot) + { + // go get the last id in the db and store it + // note: do it BEFORE initializing otherwise some instructions might get lost + // when doing it before, some instructions might run twice - not an issue + var maxId = database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); + + //if there is a max currently, or if we've never synced + if (maxId > 0 || _lastId < 0) + SaveLastSynced(maxId); + + // execute initializing callbacks + if (Options.InitializingCallbacks != null) + { + foreach (var callback in Options.InitializingCallbacks) + { + callback(); + } + } + } + + return coldboot ? SyncBootState.ColdBoot : SyncBootState.WarmBoot; } /// @@ -358,7 +367,7 @@ namespace Umbraco.Core.Sync } catch (JsonException ex) { - Logger.Error(ex, "Failed to deserialize instructions ({DtoId}: '{DtoInstructions}').", + Logger.Error(ex, "Failed to deserialize instructions ({DtoId}: '{DtoInstructions}').", dto.Id, dto.Instructions); @@ -416,11 +425,11 @@ namespace Umbraco.Core.Sync //} catch (Exception ex) { - Logger.Error ( - ex, - "DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored", - dto.Id, - dto.Instructions); + Logger.Error( + ex, + "DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored", + dto.Id, + dto.Instructions); //we cannot throw here because this invalid instruction will just keep getting processed over and over and errors // will be thrown over and over. The only thing we can do is ignore and move on. @@ -536,6 +545,8 @@ namespace Umbraco.Core.Sync + "/D" + AppDomain.CurrentDomain.Id // eg 22 + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique + public event EventHandler Booting; + private string GetDistCacheFilePath(IGlobalSettings globalSettings) { var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt"; @@ -554,29 +565,7 @@ namespace Umbraco.Core.Sync #endregion - public SyncBootState GetSyncBootState() - { - try - { - ReadLastSynced(); // get _lastId - using (var scope = ScopeProvider.CreateScope()) - { - EnsureInstructions(scope.Database); - bool isColdBoot = IsColdBoot(scope.Database); - - if (isColdBoot) - { - return SyncBootState.ColdBoot; - } - return SyncBootState.HasSyncState; - } - } - catch(Exception ex) - { - Logger.Warn("Error determining Sync Boot State", ex); - return SyncBootState.Unknown; - } - } + public SyncBootState GetSyncBootState() => _getSyncBootState.Value; #region Notify refreshers diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs index 6bfd6bff4a..a769709d09 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; namespace Umbraco.Core.Sync { @@ -24,13 +25,8 @@ namespace Umbraco.Core.Sync /// public int MaxProcessingInstructionCount { get; set; } - /// - /// A list of callbacks that will be invoked if the lastsynced.txt file does not exist. - /// - /// - /// These callbacks will typically be for eg rebuilding the xml cache file, or examine indexes, based on - /// the data in the database to get this particular server node up to date. - /// + [Obsolete("This should not be used. If initialization calls need to be invoked on a cold boot, use the ISyncBootStateAccessor.Booting event.")] + [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable InitializingCallbacks { get; set; } /// diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs index 4b8500f2d9..1598215d2c 100644 --- a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Umbraco.Core.Sync { @@ -16,5 +12,10 @@ namespace Umbraco.Core.Sync /// /// SyncBootState GetSyncBootState(); + + /// + /// Raised when the boot state is known + /// + event EventHandler Booting; } } diff --git a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs index 70cec6cc96..cc0b7dfaf1 100644 --- a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs @@ -11,6 +11,8 @@ namespace Umbraco.Core.Sync /// public class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor { + public event EventHandler Booting; + public SyncBootState GetSyncBootState() { return SyncBootState.Unknown; diff --git a/src/Umbraco.Core/Sync/SyncBootState.cs b/src/Umbraco.Core/Sync/SyncBootState.cs index 4abc53abba..45f7a87331 100644 --- a/src/Umbraco.Core/Sync/SyncBootState.cs +++ b/src/Umbraco.Core/Sync/SyncBootState.cs @@ -1,24 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core.Sync +namespace Umbraco.Core.Sync { public enum SyncBootState { /// - /// Unknown state. Treat as HasSyncState + /// Unknown state. Treat as WarmBoot /// Unknown = 0, + /// /// Cold boot. No Sync state /// ColdBoot = 1, + /// /// Warm boot. Sync state present /// - HasSyncState = 2 + WarmBoot = 2 } } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 068a161268..36f6b9d479 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -157,7 +157,7 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); + new TestSyncBootStateAccessor(SyncBootState.WarmBoot)); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 652891c476..7cb4d97ea7 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -203,7 +203,7 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); + new TestSyncBootStateAccessor(SyncBootState.WarmBoot)); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index e204bd7b5f..4453bfbbdf 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -100,7 +100,7 @@ namespace Umbraco.Tests.Scoping Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); + new TestSyncBootStateAccessor(SyncBootState.WarmBoot)); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 9cd4bb63e8..7e3bc60411 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Services Factory.GetInstance(), Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), - new TestSyncBootStateAccessor(SyncBootState.HasSyncState)); + new TestSyncBootStateAccessor(SyncBootState.WarmBoot)); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs b/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs index e5f6989381..3bff19ff2d 100644 --- a/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs +++ b/src/Umbraco.Tests/TestHelpers/TestSyncBootStateAccessor.cs @@ -15,6 +15,9 @@ namespace Umbraco.Tests.TestHelpers { _syncBootState = syncBootState; } + + public event EventHandler Booting; + public SyncBootState GetSyncBootState() { return _syncBootState; diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 26ba0db324..ef13e166a5 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -4,77 +4,13 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Services; -using Umbraco.Core.Services.Changes; using Umbraco.Core.Sync; using Umbraco.Examine; -using Umbraco.Web.Cache; using Umbraco.Web.Routing; using Umbraco.Web.Scheduling; -using Umbraco.Web.Search; -using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Compose { - /// - /// Ensures that servers are automatically registered in the database, when using the database server registrar. - /// - /// - /// At the moment servers are automatically registered upon first request and then on every - /// request but not more than once per (configurable) period. This really is "for information & debug" purposes so - /// we can look at the table and see what servers are registered - but the info is not used anywhere. - /// Should we actually want to use this, we would need a better and more deterministic way of figuring - /// out the "server address" ie the address to which server-to-server requests should be sent - because it - /// probably is not the "current request address" - especially in multi-domains configurations. - /// - [RuntimeLevel(MinLevel = RuntimeLevel.Run)] - - // during Initialize / Startup, we end up checking Examine, which needs to be initialized beforehand - // TODO: should not be a strong dependency on "examine" but on an "indexing component" - [ComposeAfter(typeof(ExamineComposer))] - - public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer, ICoreComposer - { - public static DatabaseServerMessengerOptions GetDefaultOptions(IFactory factory) - { - var logger = factory.GetInstance(); - var indexRebuilder = factory.GetInstance(); - - return new DatabaseServerMessengerOptions - { - //These callbacks will be executed if the server has not been synced - // (i.e. it is a new server or the lastsynced.txt file has been removed) - InitializingCallbacks = new Action[] - { - //rebuild the xml cache file if the server is not synced - () => - { - // rebuild the published snapshot caches entirely, if the server is not synced - // this is equivalent to DistributedCache RefreshAll... but local only - // (we really should have a way to reuse RefreshAll... locally) - // note: refresh all content & media caches does refresh content types too - var svc = Current.PublishedSnapshotService; - svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); - svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); - svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); - }, - - //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. - () => { ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000); } - } - }; - } - - public override void Compose(Composition composition) - { - base.Compose(composition); - - composition.SetDatabaseServerMessengerOptions(GetDefaultOptions); - composition.SetServerMessenger(); - composition.Register(factory=> factory.GetInstance() as BatchedDatabaseServerMessenger, Lifetime.Singleton); - } - } public sealed class DatabaseServerRegistrarAndMessengerComponent : IComponent { @@ -88,14 +24,17 @@ namespace Umbraco.Web.Compose private readonly BackgroundTaskRunner _processTaskRunner; private bool _started; private IBackgroundTask[] _tasks; - private IndexRebuilder _indexRebuilder; - public DatabaseServerRegistrarAndMessengerComponent(IRuntimeState runtime, IServerRegistrar serverRegistrar, IServerMessenger serverMessenger, IServerRegistrationService registrationService, ILogger logger, IndexRebuilder indexRebuilder) + public DatabaseServerRegistrarAndMessengerComponent( + IRuntimeState runtime, + IServerRegistrar serverRegistrar, + IServerMessenger serverMessenger, + IServerRegistrationService registrationService, + ILogger logger) { _runtime = runtime; _logger = logger; _registrationService = registrationService; - _indexRebuilder = indexRebuilder; // create task runner for DatabaseServerRegistrar _registrar = serverRegistrar as DatabaseServerRegistrar; @@ -118,7 +57,9 @@ namespace Umbraco.Web.Compose { //We will start the whole process when a successful request is made if (_registrar != null || _messenger != null) + { UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; + } // must come last, as it references some _variables _messenger?.Startup(); diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComposer.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComposer.cs new file mode 100644 index 0000000000..96e538652f --- /dev/null +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComposer.cs @@ -0,0 +1,38 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Sync; +using Umbraco.Web.Search; + +namespace Umbraco.Web.Compose +{ + /// + /// Ensures that servers are automatically registered in the database, when using the database server registrar. + /// + /// + /// At the moment servers are automatically registered upon first request and then on every + /// request but not more than once per (configurable) period. This really is "for information & debug" purposes so + /// we can look at the table and see what servers are registered - but the info is not used anywhere. + /// Should we actually want to use this, we would need a better and more deterministic way of figuring + /// out the "server address" ie the address to which server-to-server requests should be sent - because it + /// probably is not the "current request address" - especially in multi-domains configurations. + /// + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + // TODO: This is legacy, we no longer need to do this but we don't want to change the behavior now + [ComposeAfter(typeof(ExamineComposer))] + public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer, ICoreComposer + { + public static DatabaseServerMessengerOptions GetDefaultOptions(IFactory factory) + { + return new DatabaseServerMessengerOptions(); + } + + public override void Compose(Composition composition) + { + base.Compose(composition); + + composition.SetDatabaseServerMessengerOptions(GetDefaultOptions); + composition.SetServerMessenger(); + composition.Register(factory => factory.GetInstance() as BatchedDatabaseServerMessenger, Lifetime.Singleton); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index f3298cda54..b35a3d0edb 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -152,7 +152,10 @@ namespace Umbraco.Web.PublishedCache.NuCache _domainStore = new SnapDictionary(); - LoadCachesOnStartup(); + _syncBootStateAccessor.Booting += (sender, args) => + { + LoadCachesOnStartup(args); + }; } Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default; @@ -219,32 +222,31 @@ namespace Umbraco.Web.PublishedCache.NuCache /// Populates the stores /// /// This is called inside of a lock for _storesLock - private void LoadCachesOnStartup() + private void LoadCachesOnStartup(SyncBootState bootState) { + // TODO: This is super ugly that this does this as part of the ctor. + // In netcore this will be different, the initialization will happen + // outside of the ctor. + var okContent = false; var okMedia = false; - if (_syncBootStateAccessor.GetSyncBootState() == SyncBootState.ColdBoot) - { - _logger.Info("Sync Service is in a Cold Boot state. Skip LoadCachesOnStartup as the Sync Service will trigger a full reload"); - _isReady = true; - return; - } + try { - if (_localContentDbExists) + if (bootState != SyncBootState.ColdBoot && _localContentDbExists) { okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); if (!okContent) _logger.Warn("Loading content from local db raised warnings, will reload from database."); } - if (_localMediaDbExists) + if (bootState != SyncBootState.ColdBoot && _localMediaDbExists) { okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); if (!okMedia) _logger.Warn("Loading media from local db raised warnings, will reload from database."); } - + if (!okContent) LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true)); diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs index 95000b2b46..22dcb43cc0 100644 --- a/src/Umbraco.Web/Search/ExamineFinalComponent.cs +++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs @@ -3,6 +3,8 @@ using Umbraco.Core.Logging; using Umbraco.Examine; using Umbraco.Core.Composing; using Umbraco.Core; +using Umbraco.Core.Sync; +using Umbraco.Web.Routing; namespace Umbraco.Web.Search { @@ -16,27 +18,65 @@ namespace Umbraco.Web.Search private readonly IExamineManager _examineManager; BackgroundIndexRebuilder _indexRebuilder; private readonly IMainDom _mainDom; - - public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, BackgroundIndexRebuilder indexRebuilder, IMainDom mainDom) + private readonly ISyncBootStateAccessor _syncBootStateAccessor; + private readonly object _locker = new object(); + private bool _initialized = false; + + public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, BackgroundIndexRebuilder indexRebuilder, IMainDom mainDom, ISyncBootStateAccessor syncBootStateAccessor) { _logger = logger; _examineManager = examineManager; _indexRebuilder = indexRebuilder; _mainDom = mainDom; + _syncBootStateAccessor = syncBootStateAccessor; + + // must add the handler in the ctor because it will be too late in Initialize + // TODO: All of this boot synchronization for cold boot logic needs should be fixed in netcore + _syncBootStateAccessor.Booting += _syncBootStateAccessor_Booting; + } + + /// + /// Once the boot state is known we can see if we require rebuilds + /// + /// + /// + private void _syncBootStateAccessor_Booting(object sender, SyncBootState e) + { + UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt; + } + + private void UmbracoModule_RouteAttempt(object sender, RoutableAttemptEventArgs e) + { + UmbracoModule.RouteAttempt -= UmbracoModule_RouteAttempt; + + if (!_initialized) + { + lock (_locker) + { + // double check lock, we must only do this once + if (!_initialized) + { + if (!_mainDom.IsMainDom) return; + + var bootState = _syncBootStateAccessor.GetSyncBootState(); + + _examineManager.ConfigureIndexes(_mainDom, _logger); + + // if it's a cold boot, rebuild all indexes including non-empty ones + _indexRebuilder.RebuildIndexes(bootState != SyncBootState.ColdBoot, 0); + } + } + } + } public void Initialize() - { - if (!_mainDom.IsMainDom) return; - - _examineManager.ConfigureIndexes(_mainDom, _logger); - - // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? - _indexRebuilder.RebuildIndexes(true, 5000); + { } public void Terminate() { + _syncBootStateAccessor.Booting -= _syncBootStateAccessor_Booting; } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index a6cbefa825..48cc00afc9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -132,6 +132,7 @@ + @@ -1306,4 +1307,4 @@ - + \ No newline at end of file From 1a3165507ef32aca8a9ec22501ff1bca6dcc2df2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 26 Apr 2021 18:26:49 +1000 Subject: [PATCH 05/24] adds note --- src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs index 1598215d2c..249b038cae 100644 --- a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -16,6 +16,6 @@ namespace Umbraco.Core.Sync /// /// Raised when the boot state is known /// - event EventHandler Booting; + event EventHandler Booting; // TODO: This should be removed in netcore } } From 04326364b24ddd78c9a601e244b29c55fcfbac2f Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 18 May 2021 15:47:27 +0200 Subject: [PATCH 06/24] New issue forms --- .github/ISSUE_TEMPLATE/01_bug_report.yml | 52 +++++++++++++++ .github/ISSUE_TEMPLATE/02_feature_request.yml | 33 ++++++++++ .github/ISSUE_TEMPLATE/1_Bug.md | 66 ------------------- .github/ISSUE_TEMPLATE/2_Feature_request.md | 31 --------- .github/ISSUE_TEMPLATE/3_BugNetCore.md | 65 ------------------ .github/ISSUE_TEMPLATE/config.yml | 4 +- 6 files changed, 87 insertions(+), 164 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/01_bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/02_feature_request.yml delete mode 100644 .github/ISSUE_TEMPLATE/1_Bug.md delete mode 100644 .github/ISSUE_TEMPLATE/2_Feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/3_BugNetCore.md diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml new file mode 100644 index 0000000000..04d1a0e04c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -0,0 +1,52 @@ +--- +name: 🐛 Bug Report +description: "File a bug report, if you've discovered a problem in Umbraco." +labels: "type/bug" +body: +- type: input + id: "version" + attributes: + label: "Which Umbraco version are you using?" + description: "Use the help icon in the Umbraco backoffice to find the version you're using" + validations: + required: true +- type: textarea + id: "summary" + attributes: + label: "Bug summary" + description: "Write a short summary of the bug." + placeholder: > + Try to pinpoint it as much as possible. + + Try to state the actual problem, and not just what you think the solution might be. + validations: + required: true +- type: textarea + attributes: + label: "Specifics" + id: "specifics" + description: "Remember that you can format code and logs nicely with the `<>` button" + placeholder: > + Mention the URL where this bug occurs, if applicable + + Please mention if you've checked it in other browsers as well + + Please include full error messages and screenshots, gifs or mp4 videos if applicable +- type: textarea + attributes: + label: "Steps to reproduce" + id: "reproduction" + description: "How can we reproduce the problem on a clean Umbraco install?" + placeholder: > + Please include screenshots, gifs or mp4 videos if applicable + validations: + required: true +- type: textarea + attributes: + label: "Expected result / actual result" + id: "result" + description: "What did you expect that would happen on your Umbraco site and what is the actual result of the above steps?" + placeholder: > + Describe the intended/desired outcome after you did the steps mentioned. + + Describe the behaviour of the bug diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml new file mode 100644 index 0000000000..5d53b2f12e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml @@ -0,0 +1,33 @@ +--- +name: 📮 Feature Request +description: Open a feature request, if you want to propose a new feature. +labels: type/feature +body: +- type: dropdown + id: version + attributes: + label: Umbraco version + description: Which major Umbraco version are you proposing a feature for? + options: + - v8 + - v9 + validations: + required: true +- type: textarea + id: summary + attributes: + label: Description + description: Write a brief desciption of your proposed new feature. + validations: + required: true +- type: textarea + attributes: + label: How can you help? + id: help + description: Umbraco''s core team has limited available time, but maybe you can help? + placeholder: > + If we can not work on your suggestion, please don't take it personally. Most likely, it's either: + + - We think your idea is valid, but we can't find the time to work on it. + + - Your idea might be better suited as a package, if it's not suitable for the majority of users. diff --git a/.github/ISSUE_TEMPLATE/1_Bug.md b/.github/ISSUE_TEMPLATE/1_Bug.md deleted file mode 100644 index d388af0d39..0000000000 --- a/.github/ISSUE_TEMPLATE/1_Bug.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -name: 🐛 Bug Report -about: File a bug report, if you've discovered a problem in Umbraco. ---- - -A brief description of the issue goes here. - - - -## Umbraco version - -I am seeing this issue on Umbraco version: - - -Reproduction ------------- - -If you're filing a bug, please describe how to reproduce it. Include as much -relevant information as possible, such as: - -### Bug summary - - - -### Specifics - - - -### Steps to reproduce - - - -### Expected result - - - -### Actual result - - diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md deleted file mode 100644 index 16ec2568dd..0000000000 --- a/.github/ISSUE_TEMPLATE/2_Feature_request.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: 📮 Feature Request -about: Open a feature request, if you want to propose a new feature. ---- - -A brief description of your feature request goes here. - - - - -How can you help? -------------------------------- - - diff --git a/.github/ISSUE_TEMPLATE/3_BugNetCore.md b/.github/ISSUE_TEMPLATE/3_BugNetCore.md deleted file mode 100644 index 989904d4d8..0000000000 --- a/.github/ISSUE_TEMPLATE/3_BugNetCore.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: 🌟 .Net Core Bug Report -about: For bugs specifically for the upcoming .NET Core release of Umbraco, don't use this if you're working with Umbraco version 7 or 8 -labels: project/net-core ---- - -ℹ️ If this bug **also** appears on the current version 8 of Umbraco then please [report it as a regular bug](https://github.com/umbraco/Umbraco-CMS/issues/new?template=1_Bug.md), fixes in version 8 will be merged to the .NET Core version. - -A brief description of the issue goes here. - - - - -Reproduction ------------- - -If you're filing a bug, please describe how to reproduce it. Include as much -relevant information as possible, such as: - -### Bug summary - - - -### Specifics - - - -### Steps to reproduce - - - -### Expected result - - - -### Actual result - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 37d1be9158..d5418ad270 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: true +blank_issues_enabled: false contact_links: - name: ⁉️ Support Question url: https://our.umbraco.com @@ -8,4 +8,4 @@ contact_links: about: Documentation issues should be reported on the Umbraco documentation repository. - name: 🔐 Security Issue url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/ - about: Discovered a Security Issue in Umbraco? \ No newline at end of file + about: Discovered a Security Issue in Umbraco? From 92cdf4e6e82aa95f7161c3775b17196daed28eb9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 19 May 2021 10:20:01 +0200 Subject: [PATCH 07/24] Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f4e237a1f2..d156e9de88 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -44,6 +44,15 @@ We have [documented what we consider small and large changes](CONTRIBUTION_GUIDE Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted. +#### Ownership and copyright + +It is your responsibility to make sure that you're allowed to share the code you're providing us. +For example, you should have permission from your employer or customer to share code. + +Similarly, if your contribution is copied or adapted from somewhere else, make sure that the license allows you to reuse that for a contribution to Umbraco-CMS. + +If you're not sure, leave a note on your contribution and we will be happy to guide you. + ### What can I start with? Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+) From 14558aea067294b017f911827aafea50ac152b9b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 19 May 2021 10:21:44 +0200 Subject: [PATCH 08/24] Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d156e9de88..8f969bf235 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -16,6 +16,7 @@ This project and everyone participating in it, is governed by the [our Code of C [Contributing code changes](#contributing-code-changes) * [Guidelines for contributions we welcome](#guidelines-for-contributions-we-welcome) + * [Ownership and copyright](#ownership-and-copyright) * [What can I start with?](#what-can-i-start-with) * [How do I begin?](#how-do-i-begin) * [Pull requests](#pull-requests) From 017b56eee1b45cdca44e14510e72dd675412b0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 19 May 2021 12:06:17 +0200 Subject: [PATCH 09/24] #10274 Variant sorting should take into account that there might not be any language (#10278) (#10284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Niels Lyngsø Co-authored-by: Mads Rasmussen --- .../forms/umbfocuslock.directive.js | 69 ++++++++++--------- .../services/contenteditinghelper.service.js | 15 ++-- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js index 03d376e36a..e1639dde26 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js @@ -40,7 +40,9 @@ function getDomNodes(){ infiniteEditorsWrapper = document.querySelector('.umb-editors'); - infiniteEditors = Array.from(infiniteEditorsWrapper.querySelectorAll('.umb-editor')); + if(infiniteEditorsWrapper) { + infiniteEditors = Array.from(infiniteEditorsWrapper.querySelectorAll('.umb-editor') || []); + } } function getFocusableElements(targetElm) { @@ -84,22 +86,24 @@ var defaultFocusedElement = getAutoFocusElement(focusableElements); var lastKnownElement; - // If an inifite editor is being closed then we reset the focus to the element that triggered the the overlay + // If an infinite editor is being closed then we reset the focus to the element that triggered the the overlay if(closingEditor){ - var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1; - var editorInfo = infiniteEditors[0].querySelector('.editor-info'); // If there is only one editor open, search for the "editor-info" inside it and set focus on it // This is relevant when a property editor has been selected and the editor where we selected it from // is closed taking us back to the first layer // Otherwise set it to the last element in the lastKnownFocusedElements array - if(infiniteEditors.length === 1 && editorInfo !== null){ - lastKnownElement = editorInfo; + if(infiniteEditors && infiniteEditors.length === 1){ + var editorInfo = infiniteEditors[0].querySelector('.editor-info'); + if(infiniteEditors && infiniteEditors.length === 1 && editorInfo !== null) { + lastKnownElement = editorInfo; - // Clear the array - clearLastKnownFocusedElements(); + // Clear the array + clearLastKnownFocusedElements(); + } } else { + var lastItemIndex = $rootScope.lastKnownFocusableElements.length - 1; lastKnownElement = $rootScope.lastKnownFocusableElements[lastItemIndex]; // Remove the last item from the array so we always set the correct lastKnowFocus for each layer @@ -149,20 +153,24 @@ } function cleanupEventHandlers() { - var activeEditor = infiniteEditors[infiniteEditors.length - 1]; - var inactiveEditors = infiniteEditors.filter(editor => editor !== activeEditor); + //if we're in infinite editing mode + if(infiniteEditors.length > 0) { + var activeEditor = infiniteEditors[infiniteEditors.length - 1]; + var inactiveEditors = infiniteEditors.filter(editor => editor !== activeEditor); - if(inactiveEditors.length > 0) { - for (var index = 0; index < inactiveEditors.length; index++) { - var inactiveEditor = inactiveEditors[index]; + if(inactiveEditors.length > 0) { + for (var index = 0; index < inactiveEditors.length; index++) { + var inactiveEditor = inactiveEditors[index]; - // Remove event handlers from inactive editors - inactiveEditor.removeEventListener('keydown', handleKeydown); + // Remove event handlers from inactive editors + inactiveEditor.removeEventListener('keydown', handleKeydown); + } + } + else { + // Why is this one only begin called if there is no other infinite editors, wouldn't it make sense always to clean this up? + // Remove event handlers from the active editor + activeEditor.removeEventListener('keydown', handleKeydown); } - } - else { - // Remove event handlers from the active editor - activeEditor.removeEventListener('keydown', handleKeydown); } } @@ -173,10 +181,7 @@ // Fetch the DOM nodes we need getDomNodes(); - // Cleanup event handlers if we're in infinite editing mode - if(infiniteEditors.length > 0){ - cleanupEventHandlers(); - } + cleanupEventHandlers(); getFocusableElements(targetElm); @@ -204,17 +209,19 @@ // Make sure to disconnect the observer so we potentially don't end up with having many active ones disconnectObserver = true; - // Pass the correct editor in order to find the focusable elements - var newTarget = infiniteEditors[infiniteEditors.length - 2]; + if(infiniteEditors && infiniteEditors.length > 1) { + // Pass the correct editor in order to find the focusable elements + var newTarget = infiniteEditors[infiniteEditors.length - 2]; - if(infiniteEditors.length > 1){ - // Setting closing till true will let us re-apply the last known focus to then opened layer that then becomes - // active - closingEditor = true; + if(infiniteEditors.length > 1) { + // Setting closing till true will let us re-apply the last known focus to then opened layer that then becomes + // active + closingEditor = true; - onInit(newTarget); + onInit(newTarget); - return; + return; + } } // Clear lastKnownFocusableElements diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index bab665579c..67e466af35 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -789,10 +789,10 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt */ sortVariants: function (a, b) { const statesOrder = {'PublishedPendingChanges':1, 'Published': 1, 'Draft': 2, 'NotCreated': 3}; - const compareDefault = (a,b) => (!a.language.isDefault ? 1 : -1) - (!b.language.isDefault ? 1 : -1); + const compareDefault = (a,b) => (a.language && a.language.isDefault ? -1 : 1) - (b.language && b.language.isDefault ? -1 : 1); // Make sure mandatory variants goes on top, unless they are published, cause then they already goes to the top and then we want to mix them with other published variants. - const compareMandatory = (a,b) => (a.state === 'PublishedPendingChanges' || a.state === 'Published') ? 0 : (!a.language.isMandatory ? 1 : -1) - (!b.language.isMandatory ? 1 : -1); + const compareMandatory = (a,b) => (a.state === 'PublishedPendingChanges' || a.state === 'Published') ? 0 : (a.language && a.language.isMandatory ? -1 : 1) - (b.language && b.language.isMandatory ? -1 : 1); const compareState = (a, b) => (statesOrder[a.state] || 99) - (statesOrder[b.state] || 99); const compareName = (a, b) => a.displayName.localeCompare(b.displayName); @@ -813,17 +813,18 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt */ getSortedVariantsAndSegments: function (variantsAndSegments) { const sortedVariants = variantsAndSegments.filter(variant => !variant.segment).sort(this.sortVariants); - let segments = variantsAndSegments.filter(variant => variant.segment); + let variantsWithSegments = variantsAndSegments.filter(variant => variant.segment); let sortedAvailableVariants = []; sortedVariants.forEach((variant) => { - const sortedMatchedSegments = segments.filter(segment => segment.language.culture === variant.language.culture).sort(this.sortVariants); - segments = segments.filter(segment => segment.language.culture !== variant.language.culture); + const sortedMatchedSegments = variantsWithSegments.filter(segment => segment.language && variant.language && segment.language.culture === variant.language.culture).sort(this.sortVariants); + // remove variants for this culture + variantsWithSegments = variantsWithSegments.filter(segment => !segment.language || segment.language && variant.language && segment.language.culture !== variant.language.culture); sortedAvailableVariants = [...sortedAvailableVariants, ...[variant], ...sortedMatchedSegments]; }) - // if we have segments without a parent language variant we need to add the remaining segments to the array - sortedAvailableVariants = [...sortedAvailableVariants, ...segments.sort(this.sortVariants)]; + // if we have segments without a parent language variant we need to add the remaining variantsWithSegments to the array + sortedAvailableVariants = [...sortedAvailableVariants, ...variantsWithSegments.sort(this.sortVariants)]; return sortedAvailableVariants; } From 779c94c66f499730bb2449409f4febfa61dae1e7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 19 May 2021 13:29:02 +0200 Subject: [PATCH 10/24] Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8f969bf235..f83861acd9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -54,6 +54,8 @@ Similarly, if your contribution is copied or adapted from somewhere else, make s If you're not sure, leave a note on your contribution and we will be happy to guide you. +When your contribution has been accepted, it will be MIT licensed from that time onwards. + ### What can I start with? Unsure where to begin contributing to Umbraco? You can start by looking through [these `Up for grabs` issues](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs+) From a2c461bcdba057418a7a996b050b20d4a2c1ffd9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 19 May 2021 13:30:53 +0200 Subject: [PATCH 11/24] Update CONTRIBUTING.md --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f83861acd9..3432ac472a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -54,7 +54,7 @@ Similarly, if your contribution is copied or adapted from somewhere else, make s If you're not sure, leave a note on your contribution and we will be happy to guide you. -When your contribution has been accepted, it will be MIT licensed from that time onwards. +When your contribution has been accepted, it will be [MIT licensed](https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/LICENSE.md) from that time onwards. ### What can I start with? From 710ecf2537a8630d00db793877d5c169c5cf8095 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 20 May 2021 01:00:23 +0200 Subject: [PATCH 12/24] Add option to remove/cancel added crops (#10267) * Add option to remove/cancel added crops * Move vm functions to top * Only show cancel button for empty/new crop --- .../prevalue/mediapicker3.crops.controller.js | 20 ++++++--- .../prevalue/mediapicker3.crops.html | 4 +- ...umbMediaPicker3PropertyEditor.component.js | 44 +++++++++---------- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.controller.js index 922370a032..b26c6f8549 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.controller.js @@ -22,7 +22,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.MediaPicker3.CropC crop.editMode = true; }; - $scope.addNewCrop = function (evt) { + $scope.addNewCrop = function (evt) { evt.preventDefault(); var crop = {}; @@ -30,7 +30,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.MediaPicker3.CropC $scope.model.value.push(crop); $scope.validate(crop); - } + }; + $scope.setChanges = function (crop) { $scope.validate(crop); if( @@ -42,22 +43,31 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.MediaPicker3.CropC window.dispatchEvent(new Event('resize.umbImageGravity')); } }; + + $scope.isEmpty = function (crop) { + return !crop.label && !crop.alias && !crop.width && !crop.height; + }; + $scope.useForAlias = function (crop) { if (crop.alias == null || crop.alias === "") { crop.alias = (crop.label || "").toCamelCase(); } }; - $scope.validate = function(crop) { + + $scope.validate = function (crop) { $scope.validateWidth(crop); $scope.validateHeight(crop); $scope.validateAlias(crop); - } + }; + $scope.validateWidth = function (crop) { crop.hasWidthError = !(Utilities.isNumber(crop.width) && crop.width > 0); }; + $scope.validateHeight = function (crop) { crop.hasHeightError = !(Utilities.isNumber(crop.height) && crop.height > 0); }; + $scope.validateAlias = function (crop, $event) { var exists = $scope.model.value.find( x => crop !== x && crop.alias === x.alias); if (exists !== undefined || crop.alias === "") { @@ -67,7 +77,6 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.MediaPicker3.CropC // everything was good: crop.hasAliasError = false; } - }; $scope.confirmChanges = function (crop, event) { @@ -76,6 +85,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.MediaPicker3.CropC event.preventDefault(); } }; + $scope.focusNextField = function (event) { if (event.keyCode == 13) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html index 46b9ddb15f..de7e7b1767 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html @@ -20,7 +20,7 @@ -
+
@@ -33,7 +33,6 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js index 675381d46e..96f3126288 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker3/umbMediaPicker3PropertyEditor.component.js @@ -40,8 +40,13 @@ vm.loading = true; + vm.activeMediaEntry = null; vm.supportCopy = clipboardService.isSupported(); + vm.addMediaAt = addMediaAt; + vm.editMedia = editMedia; + vm.removeMedia = removeMedia; + vm.copyMedia = copyMedia; vm.labels = {}; @@ -121,7 +126,6 @@ } } - vm.addMediaAt = addMediaAt; function addMediaAt(createIndex, $event) { var mediaPicker = { startNodeId: vm.model.config.startNodeId, @@ -164,7 +168,7 @@ } } - if(vm.model.config.filter) { + if (vm.model.config.filter) { mediaPicker.filter = vm.model.config.filter; } @@ -182,6 +186,7 @@ // To be used by infinite editor. (defined here cause we need configuration from property editor) function changeMediaFor(mediaEntry, onSuccess) { + var mediaPicker = { startNodeId: vm.model.config.startNodeId, startNodeIsVirtual: vm.model.config.startNodeIsVirtual, @@ -199,16 +204,16 @@ mediaEntry.focalPoint = null; updateMediaEntryData(mediaEntry); - if(onSuccess) { + if (onSuccess) { onSuccess(); } }, close: function () { editorService.close(); } - } + }; - if(vm.model.config.filter) { + if (vm.model.config.filter) { mediaPicker.filter = vm.model.config.filter; } @@ -238,26 +243,23 @@ } }); mediaEntry.crops = newCrops; - } - vm.removeMedia = removeMedia; function removeMedia(media) { var index = vm.model.value.indexOf(media); - if(index !== -1) { + if (index !== -1) { vm.model.value.splice(index, 1); } } + function deleteAllMedias() { vm.model.value = []; } - vm.activeMediaEntry = null; function setActiveMedia(mediaEntryOrNull) { vm.activeMediaEntry = mediaEntryOrNull; } - - vm.editMedia = editMedia; + function editMedia(mediaEntry, options, $event) { if($event) @@ -304,13 +306,13 @@ editorService.open(mediaEditorModel); } - var getDocumentNameAndIcon = function() { + var getDocumentNameAndIcon = function () { // get node name var contentNodeName = "?"; var contentNodeIcon = null; - if(vm.umbVariantContent) { + if (vm.umbVariantContent) { contentNodeName = vm.umbVariantContent.editor.content.name; - if(vm.umbVariantContentEditors) { + if (vm.umbVariantContentEditors) { contentNodeIcon = vm.umbVariantContentEditors.content.icon.split(" ")[0]; } else if (vm.umbElementEditorContent) { contentNodeIcon = vm.umbElementEditorContent.model.documentType.icon.split(" ")[0]; @@ -324,9 +326,9 @@ name: contentNodeName, icon: contentNodeIcon } - } + }; - var requestCopyAllMedias = function() { + var requestCopyAllMedias = function () { var mediaKeys = vm.model.value.map(x => x.mediaKey) entityResource.getByIds(mediaKeys, "Media").then(function (entities) { @@ -338,18 +340,18 @@ var documentInfo = getDocumentNameAndIcon(); - localizationService.localize("clipboard_labelForArrayOfItemsFrom", [vm.model.label, documentInfo.name]).then(function(localizedLabel) { + localizationService.localize("clipboard_labelForArrayOfItemsFrom", [vm.model.label, documentInfo.name]).then(function (localizedLabel) { clipboardService.copyArray(clipboardService.TYPES.MEDIA, aliases, vm.model.value, localizedLabel, documentInfo.icon || "icon-thumbnail-list", vm.model.id); }); }); - } + }; - vm.copyMedia = copyMedia; function copyMedia(mediaEntry) { entityResource.getById(mediaEntry.mediaKey, "Media").then(function (mediaEntity) { clipboardService.copy(clipboardService.TYPES.MEDIA, mediaEntity.metaData.ContentTypeAlias, mediaEntry, mediaEntity.name, mediaEntity.icon, mediaEntry.key); }); } + function requestPasteFromClipboard(createIndex, pasteEntry, pasteType) { if (pasteEntry === undefined) { @@ -362,9 +364,7 @@ updateMediaEntryData(pasteEntry); vm.model.value.splice(createIndex, 0, pasteEntry); - return true; - } function requestRemoveAllMedia() { @@ -383,7 +383,6 @@ }); } - vm.sortableOptions = { cursor: "grabbing", handle: "umb-media-card", @@ -397,7 +396,6 @@ } }; - function onAmountOfMediaChanged() { // enable/disable property actions From 43ee6f288ea0b4249a6f411b03f29273fa809837 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 24 May 2021 11:25:03 -0700 Subject: [PATCH 13/24] fixes initialize flag --- src/Umbraco.Web/Search/ExamineFinalComponent.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs index 22dcb43cc0..335d19baf0 100644 --- a/src/Umbraco.Web/Search/ExamineFinalComponent.cs +++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs @@ -56,6 +56,8 @@ namespace Umbraco.Web.Search // double check lock, we must only do this once if (!_initialized) { + _initialized = true; + if (!_mainDom.IsMainDom) return; var bootState = _syncBootStateAccessor.GetSyncBootState(); @@ -63,7 +65,8 @@ namespace Umbraco.Web.Search _examineManager.ConfigureIndexes(_mainDom, _logger); // if it's a cold boot, rebuild all indexes including non-empty ones - _indexRebuilder.RebuildIndexes(bootState != SyncBootState.ColdBoot, 0); + // delay one minute since a cold boot also triggers nucache rebuilds + _indexRebuilder.RebuildIndexes(bootState != SyncBootState.ColdBoot, 60000); } } } From be3bfe1b6370d5d2cfded7a569c9fa1a969baa20 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 24 May 2021 12:32:57 -0700 Subject: [PATCH 14/24] Removes Booting event, backports lazy changes for PublishedSnapshotService from v9. --- .../Sync/ISyncBootStateAccessor.cs | 5 - .../NuCache/PublishedSnapshotService.cs | 202 ++++++++++-------- .../Search/ExamineFinalComponent.cs | 22 +- 3 files changed, 119 insertions(+), 110 deletions(-) diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs index 249b038cae..8d4cda093c 100644 --- a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -12,10 +12,5 @@ namespace Umbraco.Core.Sync /// /// SyncBootState GetSyncBootState(); - - /// - /// Raised when the boot state is known - /// - event EventHandler Booting; // TODO: This should be removed in netcore } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index b35a3d0edb..f70160cde8 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -36,6 +36,8 @@ namespace Umbraco.Web.PublishedCache.NuCache internal class PublishedSnapshotService : PublishedSnapshotServiceBase { + private readonly PublishedSnapshotServiceOptions _options; + private readonly IMainDom _mainDom; private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; private readonly IScopeProvider _scopeProvider; @@ -50,12 +52,13 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly UrlSegmentProviderCollection _urlSegmentProviders; - // volatile because we read it with no lock - private volatile bool _isReady; + private bool _isReady; + private bool _isReadSet; + private object _isReadyLock; - private readonly ContentStore _contentStore; - private readonly ContentStore _mediaStore; - private readonly SnapDictionary _domainStore; + private ContentStore _contentStore; + private ContentStore _mediaStore; + private SnapDictionary _domainStore; private readonly object _storesLock = new object(); private readonly object _elementsLock = new object(); @@ -88,9 +91,12 @@ namespace Umbraco.Web.PublishedCache.NuCache ISyncBootStateAccessor syncBootStateAccessor) : base(publishedSnapshotAccessor, variationContextAccessor) { + //if (Interlocked.Increment(ref _singletonCheck) > 1) // throw new Exception("Singleton must be instantiated only once!"); + _options = options; + _mainDom = mainDom; _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; _dataSource = dataSource; @@ -123,44 +129,6 @@ namespace Umbraco.Web.PublishedCache.NuCache if (runtime.Level != RuntimeLevel.Run) return; - // lock this entire call, we only want a single thread to be accessing the stores at once and within - // the call below to mainDom.Register, a callback may occur on a threadpool thread to MainDomRelease - // at the same time as we are trying to write to the stores. MainDomRelease also locks on _storesLock so - // it will not be able to close the stores until we are done populating (if the store is empty) - lock (_storesLock) - { - if (options.IgnoreLocalDb == false) - { - var registered = mainDom.Register(MainDomRegister, MainDomRelease); - - // stores are created with a db so they can write to it, but they do not read from it, - // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to - // figure out whether it can read the databases or it should populate them from sql - - _logger.Info("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists); - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localContentDb); - _logger.Info("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, _localMediaDb); - } - else - { - _logger.Info("Creating the content store (local db ignored)"); - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); - _logger.Info("Creating the media store (local db ignored)"); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger); - } - - _domainStore = new SnapDictionary(); - - _syncBootStateAccessor.Booting += (sender, args) => - { - LoadCachesOnStartup(args); - }; - } - - Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default; - int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? default; - if (idkMap != null) { idkMap.SetMapper(UmbracoObjectTypes.Document, id => GetUid(_contentStore, id), uid => GetId(_contentStore, uid)); @@ -168,6 +136,18 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + private int GetId(ContentStore store, Guid uid) + { + EnsureCaches(); + return store.LiveSnapshot.Get(uid)?.Id ?? default; + } + + private Guid GetUid(ContentStore store, int id) + { + EnsureCaches(); + return store.LiveSnapshot.Get(id)?.Uid ?? default; + } + /// /// Install phase of /// @@ -219,51 +199,82 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - /// Populates the stores + /// Lazily populates the stores only when they are first requested /// - /// This is called inside of a lock for _storesLock - private void LoadCachesOnStartup(SyncBootState bootState) - { - // TODO: This is super ugly that this does this as part of the ctor. - // In netcore this will be different, the initialization will happen - // outside of the ctor. - - var okContent = false; - var okMedia = false; - - try + internal void EnsureCaches() => LazyInitializer.EnsureInitialized( + ref _isReady, + ref _isReadSet, + ref _isReadyLock, + () => { - if (bootState != SyncBootState.ColdBoot && _localContentDbExists) + // lock this entire call, we only want a single thread to be accessing the stores at once and within + // the call below to mainDom.Register, a callback may occur on a threadpool thread to MainDomRelease + // at the same time as we are trying to write to the stores. MainDomRelease also locks on _storesLock so + // it will not be able to close the stores until we are done populating (if the store is empty) + lock (_storesLock) { - okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); - if (!okContent) - _logger.Warn("Loading content from local db raised warnings, will reload from database."); + if (!_options.IgnoreLocalDb) + { + var registered = _mainDom.Register(MainDomRegister, MainDomRelease); + + // stores are created with a db so they can write to it, but they do not read from it, + // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to + // figure out whether it can read the databases or it should populate them from sql + + _logger.Info("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists); + _contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger, _localContentDb); + _logger.Info("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists); + _mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger, _localMediaDb); + } + else + { + _logger.Info("Creating the content store (local db ignored)"); + _contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger); + _logger.Info("Creating the media store (local db ignored)"); + _mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _logger); + } + + _domainStore = new SnapDictionary(); + + SyncBootState bootState = _syncBootStateAccessor.GetSyncBootState(); + + var okContent = false; + var okMedia = false; + + try + { + if (bootState != SyncBootState.ColdBoot && _localContentDbExists) + { + okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); + if (!okContent) + _logger.Warn("Loading content from local db raised warnings, will reload from database."); + } + + if (bootState != SyncBootState.ColdBoot && _localMediaDbExists) + { + okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); + if (!okMedia) + _logger.Warn("Loading media from local db raised warnings, will reload from database."); + } + + if (!okContent) + LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true)); + + if (!okMedia) + LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true)); + + LockAndLoadDomains(); + } + catch (Exception ex) + { + _logger.Fatal(ex, "Panic, exception while loading cache data."); + throw; + } + + // finally, cache is ready! + return true; } - - if (bootState != SyncBootState.ColdBoot && _localMediaDbExists) - { - okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); - if (!okMedia) - _logger.Warn("Loading media from local db raised warnings, will reload from database."); - } - - if (!okContent) - LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true)); - - if (!okMedia) - LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true)); - - LockAndLoadDomains(); - } - catch (Exception ex) - { - _logger.Fatal(ex, "Panic, exception while loading cache data."); - throw; - } - - // finally, cache is ready! - _isReady = true; - } + }); private void InitializeRepositoryEvents() { @@ -1146,9 +1157,13 @@ namespace Umbraco.Web.PublishedCache.NuCache public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken) { + EnsureCaches(); + // no cache, no joy - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) + { throw new InvalidOperationException("The published snapshot service has not properly initialized."); + } var preview = previewToken.IsNullOrWhiteSpace() == false; return new PublishedSnapshot(this, preview); @@ -1159,6 +1174,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // even though the underlying elements may not change (store snapshots) public PublishedSnapshot.PublishedSnapshotElements GetElements(bool previewDefault) { + EnsureCaches(); + // note: using ObjectCacheAppCache for elements and snapshot caches // is not recommended because it creates an inner MemoryCache which is a heavy // thing - better use a dictionary-based cache which "just" creates a concurrent @@ -1805,6 +1822,8 @@ AND cmsContentNu.nodeId IS NULL public void Collect() { + EnsureCaches(); + var contentCollect = _contentStore.CollectAsync(); var mediaCollect = _mediaStore.CollectAsync(); System.Threading.Tasks.Task.WaitAll(contentCollect, mediaCollect); @@ -1814,8 +1833,17 @@ AND cmsContentNu.nodeId IS NULL #region Internals/Testing - internal ContentStore GetContentStore() => _contentStore; - internal ContentStore GetMediaStore() => _mediaStore; + internal ContentStore GetContentStore() + { + EnsureCaches(); + return _contentStore; + } + + internal ContentStore GetMediaStore() + { + EnsureCaches(); + return _mediaStore; + } #endregion } diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs index 335d19baf0..6dd775ab64 100644 --- a/src/Umbraco.Web/Search/ExamineFinalComponent.cs +++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs @@ -29,26 +29,10 @@ namespace Umbraco.Web.Search _indexRebuilder = indexRebuilder; _mainDom = mainDom; _syncBootStateAccessor = syncBootStateAccessor; - - // must add the handler in the ctor because it will be too late in Initialize - // TODO: All of this boot synchronization for cold boot logic needs should be fixed in netcore - _syncBootStateAccessor.Booting += _syncBootStateAccessor_Booting; - } - - /// - /// Once the boot state is known we can see if we require rebuilds - /// - /// - /// - private void _syncBootStateAccessor_Booting(object sender, SyncBootState e) - { - UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt; } private void UmbracoModule_RouteAttempt(object sender, RoutableAttemptEventArgs e) { - UmbracoModule.RouteAttempt -= UmbracoModule_RouteAttempt; - if (!_initialized) { lock (_locker) @@ -58,6 +42,8 @@ namespace Umbraco.Web.Search { _initialized = true; + UmbracoModule.RouteAttempt -= UmbracoModule_RouteAttempt; + if (!_mainDom.IsMainDom) return; var bootState = _syncBootStateAccessor.GetSyncBootState(); @@ -74,12 +60,12 @@ namespace Umbraco.Web.Search } public void Initialize() - { + { + UmbracoModule.RouteAttempt += UmbracoModule_RouteAttempt; } public void Terminate() { - _syncBootStateAccessor.Booting -= _syncBootStateAccessor_Booting; } } } From eba3c82a86ceea384a4a063d7b5bb50edf5ad6ed Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 24 May 2021 12:51:40 -0700 Subject: [PATCH 15/24] Changes startup logic, no more "Boot" method, the component just handles events --- .../Sync/DatabaseServerMessenger.cs | 16 +++------ .../BatchedDatabaseServerMessenger.cs | 33 ++++++++----------- ...aseServerRegistrarAndMessengerComponent.cs | 12 ++++--- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 2b52a0948e..75ccf5e4f9 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -111,17 +111,11 @@ namespace Umbraco.Core.Sync #region Sync - /// - /// Boots the messenger. - /// - /// - /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. - /// Callers MUST ensure thread-safety. - /// + [Obsolete("This is no longer used and will be removed in future versions")] protected void Boot() { - var bootState = GetSyncBootState(); - Booting?.Invoke(this, bootState); + // if called, just forces the boot logic + _ = GetSyncBootState(); } private SyncBootState BootInternal() @@ -545,8 +539,6 @@ namespace Umbraco.Core.Sync + "/D" + AppDomain.CurrentDomain.Id // eg 22 + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique - public event EventHandler Booting; - private string GetDistCacheFilePath(IGlobalSettings globalSettings) { var fileName = HttpRuntime.AppDomainAppId.ReplaceNonAlphanumericChars(string.Empty) + "-lastsynced.txt"; @@ -565,7 +557,7 @@ namespace Umbraco.Core.Sync #endregion - public SyncBootState GetSyncBootState() => _getSyncBootState.Value; + public virtual SyncBootState GetSyncBootState() => _getSyncBootState.Value; #region Notify refreshers diff --git a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs index 6596fee245..f89f62eb3d 100644 --- a/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Web/BatchedDatabaseServerMessenger.cs @@ -26,6 +26,7 @@ namespace Umbraco.Web public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { private readonly IUmbracoDatabaseFactory _databaseFactory; + private readonly Lazy _syncBootState; [Obsolete("This overload should not be used, enableDistCalls has no effect")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -39,28 +40,22 @@ namespace Umbraco.Web : base(runtime, scopeProvider, sqlContext, proflog, globalSettings, true, options) { _databaseFactory = databaseFactory; - } - - // invoked by DatabaseServerRegistrarAndMessengerComponent - internal void Startup() - { - UmbracoModule.EndRequest += UmbracoModule_EndRequest; - - if (_databaseFactory.CanConnect == false) + _syncBootState = new Lazy(() => { - Logger.Warn("Cannot connect to the database, distributed calls will not be enabled for this server."); - } - else - { - Boot(); - } + if (_databaseFactory.CanConnect == false) + { + Logger.Warn("Cannot connect to the database, distributed calls will not be enabled for this server."); + return SyncBootState.Unknown; + } + else + { + return base.GetSyncBootState(); + } + }); } - private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) - { - // will clear the batch - will remain in HttpContext though - that's ok - FlushBatch(); - } + // override to deal with database connectivity + public override SyncBootState GetSyncBootState() => _syncBootState.Value; protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index ef13e166a5..d696ae5527 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -59,15 +59,19 @@ namespace Umbraco.Web.Compose if (_registrar != null || _messenger != null) { UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce; - } - - // must come last, as it references some _variables - _messenger?.Startup(); + UmbracoModule.EndRequest += UmbracoModule_EndRequest; + } } public void Terminate() { } + private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) + { + // will clear the batch - will remain in HttpContext though - that's ok + _messenger?.FlushBatch(); + } + /// /// Handle when a request is made /// From 38f5d8329dc4722aa32c55a9ea77b027765bbda0 Mon Sep 17 00:00:00 2001 From: Vlael Layug Date: Fri, 21 May 2021 23:55:04 +0800 Subject: [PATCH 16/24] added cdata on sectionMandatoryDesc for other languages --- src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/es.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml | 4 ++-- src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml index 274b7ef5b0..6f52c24034 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml @@ -1763,9 +1763,9 @@ Er mwyn gweinyddu eich gwefan, agorwch swyddfa gefn Umbraco a dechreuwch ychwang Enw Adran Mae Adran yn ofynnol - + @section, fel arall bydd gwall yn cael ei ddangos. - + ]]> Adeiladwr ymholiad Adeiladu ymholiad o eitemau wedi dychwelyd, mewn diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 8e33f102c7..b2b11fdef5 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1328,9 +1328,9 @@ Mange hilsner fra Umbraco robotten ]]> Sektionsnavn Sektionen er obligatorisk - + @section -definition. - + ]]> Query builder sider returneret, på Returner diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index e417da263c..c4ff0812bd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -1557,10 +1557,10 @@ Bereichsname Bereich ist notwendig - + @section Definition gleichen Namens enthalten, anderfalls tritt ein Fehler auf. - + ]]> Abfrage-Generator zurückgegebene Elemente, in Ich möchte diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index c76747c5a0..6ea1bd95af 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1582,9 +1582,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont ]]> Section Name Section is mandatory - + @section definition, otherwise an error is shown. - + ]]> Query builder items returned, in copy to clipboard diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index ba7586dde2..a6cd3d81a8 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1600,9 +1600,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont ]]> Section Name Section is mandatory - + @section definition, otherwise an error is shown. - + ]]> Query builder items returned, in copy to clipboard diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 96a6ec2ec0..c2b7468523 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -1113,9 +1113,9 @@ ]]> Nombre de sección Sección es obligatoria - + @section o se mostrará un error. - + ]]> Constructor de consultas elementos devueltos, en Quiero diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml index 86684678c9..d40e3b35d7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/fr.xml @@ -1527,9 +1527,9 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à ]]> Nom de la section La section est obligatoire - + @section, sinon une erreur est affichée. - + ]]> Générateur de requêtes éléments trouvés, en copier dans le clipboard diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml index 6a138fcb6b..4ac1a9ca04 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/pl.xml @@ -1095,9 +1095,9 @@ Naciśnij przycisk instaluj, aby zainstalować bazę danych Umb ]]> Nazwa Sekcji Sekcja jest wymagana - + @section, w przeciwnym przypadku wystąpi błąd. - + ]]> Konstruktor zapytań Element zwrócony, w Chcę diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 77860523cd..ffc088522d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -1490,9 +1490,9 @@ ]]> Название секции Секция обязательна - + @section, в противном случае генерируется ошибка. - + ]]> Генератор запросов элементов в результате, за Мне нужны diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml index bb73d43a3e..d59cf248fc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/tr.xml @@ -1622,9 +1622,9 @@ Web sitenizi yönetmek için, Umbraco'nun arka ofisini açın ve içerik eklemey Bölüm Adı Bölüm zorunludur - + @section tanımı içermelidir, aksi takdirde bir hata gösterilir. - + ]]> Sorgu oluşturucu öğe iade edildi, panoya kopyala From 3440cbbac94bb29c500135a1e2c8093b341f397a Mon Sep 17 00:00:00 2001 From: patrickdemooij9 Date: Fri, 28 May 2021 01:14:07 +0200 Subject: [PATCH 17/24] Fixed 10144: Prevalue alias not set (#10163) * Fixed 10144: Prevalue alias not set * Remove console.log Co-authored-by: Nathan Woulfe --- .../src/common/services/datatypehelper.service.js | 5 +++-- .../datatypesettings/datatypesettings.controller.js | 3 ++- .../src/views/datatypes/datatype.edit.controller.js | 13 +------------ 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js index 3cde632d4b..f4317b51b7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/datatypehelper.service.js @@ -18,7 +18,8 @@ function dataTypeHelper() { description: preVals[i].description, label: preVals[i].label, view: preVals[i].view, - value: preVals[i].value + value: preVals[i].value, + config: preVals[i].config }); } @@ -52,4 +53,4 @@ function dataTypeHelper() { return dataTypeHelperService; } -angular.module('umbraco.services').factory('dataTypeHelper', dataTypeHelper); \ No newline at end of file +angular.module('umbraco.services').factory('dataTypeHelper', dataTypeHelper); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js index 7faaddde77..4e1b3e868d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.controller.js @@ -76,7 +76,7 @@ // get pre values dataTypeResource.getPreValues(newDataType.selectedEditor).then(function(preValues) { - newDataType.preValues = preValues; + newDataType.preValues = dataTypeHelper.createPreValueProps(preValues); vm.dataType = newDataType; vm.loadingDataType = false; }); @@ -88,6 +88,7 @@ function getDataType() { vm.loadingDataType = true; dataTypeResource.getById($scope.model.id).then(function (dataType) { + dataType.preValues = dataTypeHelper.createPreValueProps(dataType.preValues); vm.dataType = dataType; vm.loadingDataType = false; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index a2af8c8239..857351bb93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -37,18 +37,7 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic //method used to configure the pre-values when we retrieve them from the server function createPreValueProps(preVals) { - vm.preValues = []; - for (var i = 0; i < preVals.length; i++) { - vm.preValues.push({ - hideLabel: preVals[i].hideLabel, - alias: preVals[i].key, - description: preVals[i].description, - label: preVals[i].label, - view: preVals[i].view, - value: preVals[i].value, - config: preVals[i].config - }); - } + vm.preValues = dataTypeHelper.createPreValueProps(preVals); } From 7cccbb3fbf7bc8c7be6ae3f01df0face2fa53d9a Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 28 May 2021 01:25:56 +0200 Subject: [PATCH 18/24] Replaced angular.extend with Utilities.extend (#10023) * Replace angular.extend with Utilities.extend * Replace more references of angular.extend with Utilities.extend * Replace angular.extend * Replace last reference of angular.extend except in Utilities itself * Use spread operator * Add spread operator * Use existing format with empty destination object * Configurate Babel to work with JS spread operator * Use default for loose property which is "false" * use alias for angular.extend - removes need for any other changes and makes multiple arguments easy Co-authored-by: Nathan Woulfe --- src/Umbraco.Web.UI.Client/.babelrc | 8 ++++++++ src/Umbraco.Web.UI.Client/package.json | 1 + .../directives/components/grid/grid.rte.directive.js | 2 +- .../directives/components/umbaceeditor.directive.js | 5 ++--- .../src/common/resources/content.resource.js | 2 +- .../src/common/resources/entity.resource.js | 4 ++-- .../src/common/resources/log.resource.js | 4 ++-- .../src/common/resources/logviewer.resource.js | 2 +- .../src/common/resources/media.resource.js | 4 ++-- .../src/common/resources/member.resource.js | 2 +- .../src/common/resources/relationtype.resource.js | 2 +- .../src/common/resources/users.resource.js | 2 +- .../src/common/services/keyboard.service.js | 2 +- .../common/services/searchresultformatter.service.js | 10 +++++----- .../src/common/services/tinymce.service.js | 7 +++---- src/Umbraco.Web.UI.Client/src/utilities.js | 3 ++- .../views/prevalueeditors/colorpicker.controller.js | 2 +- .../views/prevalueeditors/mediapicker.controller.js | 2 +- .../src/views/prevalueeditors/treepicker.controller.js | 2 +- .../contentpicker/contentpicker.controller.js | 4 ++-- .../datepicker/datepicker.controller.js | 2 +- .../dropdownFlexible/dropdownFlexible.controller.js | 2 +- .../src/views/propertyeditors/grid/grid.controller.js | 2 +- .../imagecropper/imagecropper.controller.js | 2 +- .../mediapicker/mediapicker.controller.js | 4 ++-- .../memberpicker/memberpicker.controller.js | 2 +- .../src/views/propertyeditors/rte/rte.controller.js | 4 ++-- 27 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.babelrc b/src/Umbraco.Web.UI.Client/.babelrc index ae044dabd8..748cd6a810 100644 --- a/src/Umbraco.Web.UI.Client/.babelrc +++ b/src/Umbraco.Web.UI.Client/.babelrc @@ -6,5 +6,13 @@ "targets": "last 2 version, not dead, > 0.5%, not ie 11" } ] + ], + "plugins": [ + [ + "@babel/plugin-proposal-object-rest-spread", + { + "useBuiltIns": true + } + ] ] } diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 6514f2f217..045f788929 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -53,6 +53,7 @@ "devDependencies": { "@babel/core": "7.6.4", "@babel/preset-env": "7.6.3", + "@babel/plugin-proposal-object-rest-spread": "7.13.8", "autoprefixer": "9.6.5", "caniuse-lite": "^1.0.30001037", "cssnano": "4.1.10", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index e2b2e2c3df..7b690e5863 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -75,7 +75,7 @@ angular.module("umbraco.directives") maxImageSize: editorConfig.maxImageSize }; - angular.extend(baseLineConfigObj, standardConfig); + Utilities.extend(baseLineConfigObj, standardConfig); baseLineConfigObj.setup = function (editor) { 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 5e1f2489e6..ef463e6d95 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 @@ -146,8 +146,7 @@ * umbAceEditorConfig merged with user options via json in attribute or data binding * @type object */ - var opts = angular.extend({}, options, scope.umbAceEditor); - + var opts = Utilities.extend({}, options, scope.umbAceEditor); //load ace libraries here... @@ -273,7 +272,7 @@ return; } - opts = angular.extend({}, options, scope.umbAceEditor); + opts = Utilities.extend({}, options, scope.umbAceEditor); opts.callbacks = [opts.onLoad]; if (opts.onLoad !== options.onLoad) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 368eab2339..b6e5183b30 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -776,7 +776,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; //change asc/desct diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 838e8f1b80..0b060da34b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -439,7 +439,7 @@ function entityResource($q, $http, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; //change asc/desct @@ -512,7 +512,7 @@ function entityResource($q, $http, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; //change asc/desct diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js index bcdaddd22f..b1931af541 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/log.resource.js @@ -71,7 +71,7 @@ function logResource($q, $http, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; @@ -140,7 +140,7 @@ function logResource($q, $http, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js index d2d008640d..46ef8d2919 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/logviewer.resource.js @@ -58,7 +58,7 @@ function logViewerResource($q, $http, umbRequestHelper) { } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 06eb2ed4d2..87ba054675 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -333,7 +333,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; //change asc/desct @@ -563,7 +563,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index c45e173a98..05e746a69b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -40,7 +40,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; //change asc/desct diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js index 7c542c5e7b..96c7882b5a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/relationtype.resource.js @@ -126,7 +126,7 @@ function relationTypeResource($q, $http, umbRequestHelper, umbDataFormatter) { options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 91f00a36e3..0b69bec3f5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -226,7 +226,7 @@ options = {}; } //overwrite the defaults if there are any specified - angular.extend(defaults, options); + Utilities.extend(defaults, options); //now copy back to the options we will use options = defaults; //change asc/desct diff --git a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js index 31375c5c56..b49e809eeb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js @@ -264,7 +264,7 @@ function keyboardService($window, $timeout) { var elt; // Initialize opt object - opt = angular.extend({}, defaultOpt, opt); + opt = Utilities.extend(defaultOpt, opt); label = label.toLowerCase(); elt = opt.target; if(typeof opt.target === 'string'){ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/searchresultformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/searchresultformatter.service.js index 9bf9f3762c..ca8fdc7fa7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/searchresultformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/searchresultformatter.service.js @@ -3,27 +3,27 @@ function searchResultFormatter(umbRequestHelper) { function configureDefaultResult(content, treeAlias, appAlias) { content.editorPath = appAlias + "/" + treeAlias + "/edit/" + content.id; - angular.extend(content.metaData, { treeAlias: treeAlias }); + Utilities.extend(content.metaData, { treeAlias: treeAlias }); } function configureContentResult(content, treeAlias, appAlias) { content.menuUrl = umbRequestHelper.getApiUrl("contentTreeBaseUrl", "GetMenu", [{ id: content.id }, { application: appAlias }]); content.editorPath = appAlias + "/" + treeAlias + "/edit/" + content.id; - angular.extend(content.metaData, { treeAlias: treeAlias }); + Utilities.extend(content.metaData, { treeAlias: treeAlias }); content.subTitle = content.metaData.Url; } function configureMemberResult(member, treeAlias, appAlias) { member.menuUrl = umbRequestHelper.getApiUrl("memberTreeBaseUrl", "GetMenu", [{ id: member.id }, { application: appAlias }]); member.editorPath = appAlias + "/" + treeAlias + "/edit/" + (member.key ? member.key : member.id); - angular.extend(member.metaData, { treeAlias: treeAlias }); + Utilities.extend(member.metaData, { treeAlias: treeAlias }); member.subTitle = member.metaData.Email; } function configureMediaResult(media, treeAlias, appAlias) { media.menuUrl = umbRequestHelper.getApiUrl("mediaTreeBaseUrl", "GetMenu", [{ id: media.id }, { application: appAlias }]); media.editorPath = appAlias + "/" + treeAlias + "/edit/" + media.id; - angular.extend(media.metaData, { treeAlias: treeAlias }); + Utilities.extend(media.metaData, { treeAlias: treeAlias }); } return { @@ -34,4 +34,4 @@ function searchResultFormatter(umbRequestHelper) { }; } -angular.module('umbraco.services').factory('searchResultFormatter', searchResultFormatter); \ No newline at end of file +angular.module('umbraco.services').factory('searchResultFormatter', searchResultFormatter); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 9b5d240776..fbe342d44f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -463,8 +463,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }; - angular.extend(config, pasteConfig); - + Utilities.extend(config, pasteConfig); if (tinyMceConfig.customConfig) { @@ -481,7 +480,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise //if it's an object it will overwrite the baseline if (Utilities.isArray(config[i]) && Utilities.isArray(tinyMceConfig.customConfig[i])) { - //concat it and below this concat'd array will overwrite the baseline in angular.extend + //concat it and below this concat'd array will overwrite the baseline in Utilities.extend tinyMceConfig.customConfig[i] = config[i].concat(tinyMceConfig.customConfig[i]); } } @@ -498,7 +497,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } } - angular.extend(config, tinyMceConfig.customConfig); + Utilities.extend(config, tinyMceConfig.customConfig); } return config; diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js index 01e18e4e1c..abbc287e0f 100644 --- a/src/Umbraco.Web.UI.Client/src/utilities.js +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -33,8 +33,9 @@ /** * Facade to angular.extend * Use this with Angular objects, for vanilla JS objects, use Object.assign() + * This is an alias as it to allow passing an unknown number of arguments */ - const extend = (dst, src) => angular.extend(dst, src); + const extend = angular.extend; /** * Equivalent to angular.isFunction diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js index 9ad2c87ab4..c3f0314389 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/colorpicker.controller.js @@ -7,7 +7,7 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.ColorPickerControl }; //map the user config - angular.extend(config, $scope.model.config); + Utilities.extend(config, $scope.model.config); //map back to the model $scope.model.config = config; diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js index 4258229042..65e5fa906f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/mediapicker.controller.js @@ -23,7 +23,7 @@ function mediaPickerController($scope, entityResource, iconHelper, editorService //combine the dialogOptions with any values returned from the server if ($scope.model.config) { - angular.extend(dialogOptions, $scope.model.config); + Utilities.extend(dialogOptions, $scope.model.config); } $scope.openTreePicker = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js index 0359043da4..1b490d86d3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treepicker.controller.js @@ -21,7 +21,7 @@ angular.module('umbraco') //combine the config with any values returned from the server if ($scope.model.config) { - angular.extend(config, $scope.model.config); + Utilities.extend(config, $scope.model.config); } if ($scope.model.value) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index e818fe9a23..d8c7b3e76a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -130,7 +130,7 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso delete $scope.model.config.startNode; } //merge the server config on top of the default config, then set the server config to use the result - $scope.model.config = angular.extend(defaultConfig, $scope.model.config); + $scope.model.config = Utilities.extend(defaultConfig, $scope.model.config); // if the property is mandatory, set the minCount config to 1 (unless of course it is set to something already), // that way the minCount/maxCount validation handles the mandatory as well @@ -189,7 +189,7 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the // pre-value config on to the dialog options - angular.extend(dialogOptions, $scope.model.config); + Utilities.extend(dialogOptions, $scope.model.config); dialogOptions.dataTypeKey = $scope.model.dataTypeKey; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js index af1dea167a..3ec7e191f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.controller.js @@ -24,7 +24,7 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM }; // map the user config - $scope.model.config = angular.extend(config, $scope.model.config); + $scope.model.config = Utilities.extend(config, $scope.model.config);; // ensure the format doesn't get overwritten with an empty string if ($scope.model.config.format === "" || $scope.model.config.format === undefined || $scope.model.config.format === null) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js index 4064df6a24..d62d521d17 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -8,7 +8,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo }; //map the user config - angular.extend(config, $scope.model.config); + Utilities.extend(config, $scope.model.config); //map back to the model $scope.model.config = config; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 15f5ceaa88..983644767d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -753,7 +753,7 @@ angular.module("umbraco") // allowed for this template based on the current config. _.each(found.sections, function (templateSection, index) { - angular.extend($scope.model.value.sections[index], Utilities.copy(templateSection)); + Utilities.extend($scope.model.value.sections[index], Utilities.copy(templateSection)); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index e9d9950bdd..cbaf843d35 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -43,7 +43,7 @@ angular.module('umbraco') function setModelValueWithSrc(src) { if (!$scope.model.value || !$scope.model.value.src) { //we are copying to not overwrite the original config - $scope.model.value = angular.extend(Utilities.copy($scope.model.config), { src: src }); + $scope.model.value = Utilities.extend(Utilities.copy($scope.model.config), { src: src }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index c6320a7cf2..bdd3251ca7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -116,7 +116,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl media.loading = true; entityResource.getById(media.udi, "Media") .then(function (mediaEntity) { - angular.extend(media, mediaEntity); + Utilities.extend(media, mediaEntity); media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); media.loading = false; }); @@ -226,7 +226,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // we need to update all the media items vm.mediaItems.forEach(media => { if (media.id === model.mediaNode.id) { - angular.extend(media, mediaEntity); + Utilities.extend(media, mediaEntity); media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js index 315eb18ee4..0aa01a560b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/memberpicker/memberpicker.controller.js @@ -40,7 +40,7 @@ function memberPickerController($scope, entityResource, iconHelper, editorServic //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the // pre-value config on to the dialog options if ($scope.model.config) { - angular.extend(dialogOptions, $scope.model.config); + Utilities.extend(dialogOptions, $scope.model.config); } $scope.openMemberPicker = function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 9faa012a4d..1b7a96309a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -86,8 +86,8 @@ angular.module("umbraco") }); }; - - angular.extend(baseLineConfigObj, standardConfig); + + Utilities.extend(baseLineConfigObj, standardConfig); // We need to wait for DOM to have rendered before we can find the element by ID. $timeout(function () { From 2697431de68820986cb2a1b45d014398c0b1765f Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 28 May 2021 09:08:06 +0200 Subject: [PATCH 19/24] Fix issue with extend of object in keyboard service --- .../src/common/services/keyboard.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js index b49e809eeb..66babe03b8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/keyboard.service.js @@ -264,7 +264,7 @@ function keyboardService($window, $timeout) { var elt; // Initialize opt object - opt = Utilities.extend(defaultOpt, opt); + opt = Utilities.extend({}, defaultOpt, opt); label = label.toLowerCase(); elt = opt.target; if(typeof opt.target === 'string'){ From 1c630e3c5f5013a4b4a5b0218555066a9f6e72f9 Mon Sep 17 00:00:00 2001 From: Vlael Layug Date: Fri, 28 May 2021 00:07:30 +0800 Subject: [PATCH 20/24] inject clearAddUserForm() to inviteUser condition --- .../src/views/users/views/users/users.controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 70102c9418..0bc6e609db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -245,6 +245,8 @@ $location.search("invite", null); } else if (state === "inviteUser") { + clearAddUserForm(); + $location.search("create", null); $location.search("invite", "true"); } From 0990599c630e2b372b7c3f7ed11ad47bea05d0de Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 31 May 2021 18:12:06 +0200 Subject: [PATCH 21/24] Support custom SVG icon in content app --- .../views/components/editor/umb-editor-navigation-item.html | 2 +- .../src/views/components/editor/umb-editor-navigation.html | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html index 0fc84c7fb8..9dd90b6b57 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html @@ -8,7 +8,7 @@ class="umb-sub-views-nav-item__action umb-outline umb-outline--thin" aria-haspopup="{{ vm.item.anchors && vm.item.anchors.length > 1 }}" aria-expanded="{{ vm.expanded }}"> - + {{ vm.item.name }}
{{vm.item.badge.count}}
!
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html index 08858b058b..be196fb31f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html @@ -23,8 +23,7 @@ text="{{moreButton.name}}" show-text="true" mode="tab" - css-class="umb-sub-views-nav-item__action umb-outline umb-outline--thin {{moreButton.active ? 'is-active' : ''}}" - > + css-class="umb-sub-views-nav-item__action umb-outline umb-outline--thin {{moreButton.active ? 'is-active' : ''}}"> From 3c2e7759fb1190e2a428a4fe2911b652cffa46fa Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 2 Jun 2021 03:00:01 +0200 Subject: [PATCH 22/24] Support custom SVG icon in block list editor (#10365) * Use custom SVG icon in block card * Adjust icons in block list editor views --- .../components/blockcard/umb-block-card.html | 8 +++- .../inlineblock/inlineblock.editor.html | 2 +- .../inlineblock/inlineblock.editor.less | 41 +++++++++++++++---- .../labelblock/labelblock.editor.html | 2 +- .../labelblock/labelblock.editor.less | 2 +- .../unsupportedblock.editor.html | 2 +- .../unsupportedblock.editor.less | 2 +- 7 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html index c1656c33ff..276cc2491a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html @@ -2,7 +2,13 @@
- +
+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html index 6cf717df81..c6b5e0a4fb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html @@ -5,7 +5,7 @@ ng-click="vm.openBlock(block)" ng-focus="block.focus"> - + {{block.label}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less index 9b408becce..5b155ac5ad 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less @@ -27,8 +27,8 @@ transition: transform 80ms ease-out; } - i { - font-size: 22px; + .icon { + font-size: 1.1rem; display: inline-block; vertical-align: middle; } @@ -48,17 +48,21 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & { > button { color: @formErrorText; + .show-validation-type-warning & { color: @formWarningText; } + span.caret { border-top-color: @formErrorText; + .show-validation-type-warning & { border-top-color: @formWarningText; } } } } + ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & { > button { span.name { @@ -78,22 +82,41 @@ padding: 2px; line-height: 10px; background-color: @formErrorText; + .show-validation-type-warning & { background-color: @formWarningText; } + font-weight: 900; - animation-duration: 1.4s; animation-iteration-count: infinite; animation-name: blockelement-inlineblock-editor--badge-bounce; animation-timing-function: ease; + @keyframes blockelement-inlineblock-editor--badge-bounce { - 0% { transform: translateY(0); } - 20% { transform: translateY(-4px); } - 40% { transform: translateY(0); } - 55% { transform: translateY(-2px); } - 70% { transform: translateY(0); } - 100% { transform: translateY(0); } + 0% { + transform: translateY(0); + } + + 20% { + transform: translateY(-4px); + } + + 40% { + transform: translateY(0); + } + + 55% { + transform: translateY(-2px); + } + + 70% { + transform: translateY(0); + } + + 100% { + transform: translateY(0); + } } } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html index f51ea4607c..a5c4993505 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html @@ -3,6 +3,6 @@ ng-focus="block.focus" ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }" val-server-property-class=""> - + {{block.label}} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less index 837fd3f564..8391923339 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less @@ -16,7 +16,7 @@ user-select: none; transition: border-color 120ms, background-color 120ms; - > i { + > .icon { font-size: 22px; margin-right: 10px; display: inline-block; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html index d5b0ddfd04..2252db3745 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html @@ -1,6 +1,6 @@
- + {{block.label}}
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less index d3eddd0815..8b8fcef9e7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less @@ -18,7 +18,7 @@ background-color: @gray-11; color: @gray-6; - i { + .icon { font-size: 22px; margin-right: 5px; display: inline-block; From 57f777684890069a19ce1c27adf186da6ec4c856 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 2 Jun 2021 03:20:56 +0200 Subject: [PATCH 23/24] Add input id so click on property label set focus in input (#9477) Co-authored-by: Nathan Woulfe --- src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html index 1577af43de..f59491c693 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html @@ -5,6 +5,7 @@ ng-submit="vm.save()" novalidate val-form-manager> +

-