diff --git a/build/build.ps1 b/build/build.ps1 index 892654d3cd..ea07e4516f 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -456,10 +456,10 @@ $ubuild.DefineMethod("PrepareAngularDocs", { Write-Host "Prepare Angular Documentation" - + $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput - + "Moving to Umbraco.Web.UI.Docs folder" cd ..\src\Umbraco.Web.UI.Docs diff --git a/src/Umbraco.Abstractions/Composing/ComponentCollection.cs b/src/Umbraco.Abstractions/Composing/ComponentCollection.cs index 9b5319dc41..fa4a1849b6 100644 --- a/src/Umbraco.Abstractions/Composing/ComponentCollection.cs +++ b/src/Umbraco.Abstractions/Composing/ComponentCollection.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Logging; @@ -43,8 +44,15 @@ namespace Umbraco.Core.Composing var componentType = component.GetType(); using (_logger.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) { - component.Terminate(); - component.DisposeIfDisposable(); + try + { + component.Terminate(); + component.DisposeIfDisposable(); + } + catch (Exception ex) + { + _logger.Error(componentType, ex, "Error while terminating component {ComponentType}.", componentType.FullName); + } } } } diff --git a/src/Umbraco.Abstractions/Composing/Composers.cs b/src/Umbraco.Abstractions/Composing/Composers.cs index 0510740e42..b2e6c9d068 100644 --- a/src/Umbraco.Abstractions/Composing/Composers.cs +++ b/src/Umbraco.Abstractions/Composing/Composers.cs @@ -18,19 +18,52 @@ namespace Umbraco.Core.Composing private readonly Composition _composition; private readonly IProfilingLogger _logger; private readonly IEnumerable _composerTypes; + private readonly IEnumerable _enableDisableAttributes; private const int LogThresholdMilliseconds = 100; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The composition. - /// The composer types. - /// A profiling logger. + /// The types. + /// The profiling logger. + [Obsolete("This overload only gets the EnableComposer/DisableComposer attributes from the composerTypes assemblies.")] public Composers(Composition composition, IEnumerable composerTypes, IProfilingLogger logger) + : this(composition, composerTypes, Enumerable.Empty(), logger) + { + var enableDisableAttributes = new List(); + + var assemblies = composerTypes.Select(t => t.Assembly).Distinct(); + foreach (var assembly in assemblies) + { + enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(EnableComposerAttribute))); + enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(DisableComposerAttribute))); + } + + _enableDisableAttributes = enableDisableAttributes; + } + + /// + /// Initializes a new instance of the class. + /// + /// The composition. + /// The types. + /// The and/or attributes. + /// The profiling logger. + /// composition + /// or + /// composerTypes + /// or + /// enableDisableAttributes + /// or + /// logger + + public Composers(Composition composition, IEnumerable composerTypes, IEnumerable enableDisableAttributes, IProfilingLogger logger) { _composition = composition ?? throw new ArgumentNullException(nameof(composition)); _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); + _enableDisableAttributes = enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -103,7 +136,7 @@ namespace Umbraco.Core.Composing .ToList(); // enable or disable composers - EnableDisableComposers(composerTypeList); + EnableDisableComposers(_enableDisableAttributes, composerTypeList); void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, List set2) where TAttribute : Attribute @@ -218,7 +251,7 @@ namespace Umbraco.Core.Composing return text.ToString(); } - private static void EnableDisableComposers(ICollection types) + private static void EnableDisableComposers(IEnumerable enableDisableAttributes, ICollection types) { var enabled = new Dictionary(); @@ -240,20 +273,16 @@ namespace Umbraco.Core.Composing enableInfo.Weight = weight2; } - var assemblies = types.Select(x => x.Assembly).Distinct(); - foreach (var assembly in assemblies) + foreach (var attr in enableDisableAttributes.OfType()) { - foreach (var attr in assembly.GetCustomAttributes()) - { - var type = attr.EnabledType; - UpdateEnableInfo(type, 2, enabled, true); - } + var type = attr.EnabledType; + UpdateEnableInfo(type, 2, enabled, true); + } - foreach (var attr in assembly.GetCustomAttributes()) - { - var type = attr.DisabledType; - UpdateEnableInfo(type, 2, enabled, false); - } + foreach (var attr in enableDisableAttributes.OfType()) + { + var type = attr.DisabledType; + UpdateEnableInfo(type, 2, enabled, false); } foreach (var composerType in types) diff --git a/src/Umbraco.Abstractions/Composing/TypeLoader.cs b/src/Umbraco.Abstractions/Composing/TypeLoader.cs index 6feb915f4a..76d00c472d 100644 --- a/src/Umbraco.Abstractions/Composing/TypeLoader.cs +++ b/src/Umbraco.Abstractions/Composing/TypeLoader.cs @@ -514,6 +514,49 @@ namespace Umbraco.Core.Composing #endregion + #region Get Assembly Attributes + + /// + /// Gets the assembly attributes of the specified type . + /// + /// The attribute type. + /// + /// The assembly attributes of the specified type . + /// + public IEnumerable GetAssemblyAttributes() + where T : Attribute + { + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + } + + /// + /// Gets all the assembly attributes. + /// + /// + /// All assembly attributes. + /// + public IEnumerable GetAssemblyAttributes() + { + return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + } + + /// + /// Gets the assembly attributes of the specified . + /// + /// The attribute types. + /// + /// The assembly attributes of the specified types. + /// + /// attributeTypes + public IEnumerable GetAssemblyAttributes(params Type[] attributeTypes) + { + if (attributeTypes == null) throw new ArgumentNullException(nameof(attributeTypes)); + + return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList(); + } + + #endregion + #region Get Types /// diff --git a/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IKeepAliveSection.cs b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IKeepAliveSection.cs new file mode 100644 index 0000000000..3a0ad258c5 --- /dev/null +++ b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IKeepAliveSection.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface IKeepAliveSection : IUmbracoConfigurationSection + { + bool DisableKeepAliveTask { get; } + string KeepAlivePingUrl { get; } + } +} diff --git a/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs index 81d27e7bae..acd9c92588 100644 --- a/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs +++ b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/IUmbracoSettingsSection.cs @@ -15,5 +15,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings ILoggingSection Logging { get; } IWebRoutingSection WebRouting { get; } + + IKeepAliveSection KeepAlive { get; } } } diff --git a/src/Umbraco.Abstractions/IMainDom.cs b/src/Umbraco.Abstractions/IMainDom.cs index 3a8cd13ff1..31b2e2eee0 100644 --- a/src/Umbraco.Abstractions/IMainDom.cs +++ b/src/Umbraco.Abstractions/IMainDom.cs @@ -14,6 +14,9 @@ namespace Umbraco.Core /// /// Gets a value indicating whether the current domain is the main domain. /// + /// + /// When the first call is made to this there will generally be some logic executed to acquire a distributed lock lease. + /// bool IsMainDom { get; } /// @@ -35,4 +38,4 @@ namespace Umbraco.Core /// is guaranteed to execute before the AppDomain releases the main domain status. bool Register(Action install, Action release, int weight = 100); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Abstractions/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Abstractions/Manifest/ManifestContentAppFactory.cs index e2c3ee48fa..903efe2897 100644 --- a/src/Umbraco.Abstractions/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Abstractions/Manifest/ManifestContentAppFactory.cs @@ -20,6 +20,8 @@ namespace Umbraco.Core.Manifest // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types // '+media/*', // show for all media types + // '-member/foo' // hide for member type 'foo' + // '+member/*' // show for all member types // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, @@ -59,6 +61,10 @@ namespace Umbraco.Core.Manifest partA = "media"; partB = media.ContentType.Alias; break; + case IMember member: + partA = "member"; + partB = member.ContentType.Alias; + break; default: return null; diff --git a/src/Umbraco.Abstractions/Models/IDataEditorWithMediaPath.cs b/src/Umbraco.Abstractions/Models/IDataEditorWithMediaPath.cs new file mode 100644 index 0000000000..e8af1b0ac3 --- /dev/null +++ b/src/Umbraco.Abstractions/Models/IDataEditorWithMediaPath.cs @@ -0,0 +1,19 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Must be implemented by property editors that store media and return media paths + /// + /// + /// Currently there are only 2x core editors that do this: upload and image cropper. + /// It would be possible for developers to know implement their own media property editors whereas previously this was not possible. + /// + public interface IDataEditorWithMediaPath + { + /// + /// Returns the media path for the value stored for a property + /// + /// + /// + string GetMediaPath(object value); + } +} diff --git a/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs b/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs new file mode 100644 index 0000000000..89ba9be54d --- /dev/null +++ b/src/Umbraco.Configuration/UmbracoSettings/KeepAliveElement.cs @@ -0,0 +1,13 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + internal class KeepAliveElement : ConfigurationElement, IKeepAliveSection + { + [ConfigurationProperty("disableKeepAliveTask", DefaultValue = "false")] + public bool DisableKeepAliveTask => (bool)base["disableKeepAliveTask"]; + + [ConfigurationProperty("keepAlivePingUrl", DefaultValue = "{umbracoApplicationUrl}/api/keepalive/ping")] + public string KeepAlivePingUrl => (string)base["keepAlivePingUrl"]; + } +} diff --git a/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs b/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs index 98fdcaff93..e605b86edf 100644 --- a/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs +++ b/src/Umbraco.Configuration/UmbracoSettings/UmbracoSettingsSection.cs @@ -19,10 +19,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings [ConfigurationProperty("logging")] public LoggingElement Logging => (LoggingElement)this["logging"]; - [ConfigurationProperty("web.routing")] public WebRoutingElement WebRouting => (WebRoutingElement)this["web.routing"]; + [ConfigurationProperty("keepAlive")] + internal KeepAliveElement KeepAlive => (KeepAliveElement)this["keepAlive"]; + IContentSection IUmbracoSettingsSection.Content => Content; ISecuritySection IUmbracoSettingsSection.Security => Security; @@ -34,5 +36,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings ILoggingSection IUmbracoSettingsSection.Logging => Logging; IWebRoutingSection IUmbracoSettingsSection.WebRouting => WebRouting; + + IKeepAliveSection IUmbracoSettingsSection.KeepAlive => KeepAlive; } } diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index 440025a042..eba7492563 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -16,25 +16,26 @@ namespace Umbraco.Core /// When an AppDomain starts, it tries to acquire the main domain status. /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. /// - internal class MainDom : IMainDom, IRegisteredObject + internal class MainDom : IMainDom, IRegisteredObject, IDisposable { #region Vars private readonly ILogger _logger; // our own lock for local consistency - private readonly object _locko = new object(); + private object _locko = new object(); // async lock representing the main domain lock - private readonly AsyncLock _asyncLock; - private IDisposable _asyncLocker; + private readonly SystemLock _systemLock; + private IDisposable _systemLocker; // event wait handle used to notify current main domain that it should // release the lock because a new domain wants to be the main domain private readonly EventWaitHandle _signal; + private bool _isInitialized; // indicates whether... - private volatile bool _isMainDom; // we are the main domain + private bool _isMainDom; // we are the main domain private volatile bool _signaled; // we have been signaled // actions to run before releasing the main domain @@ -49,12 +50,12 @@ namespace Umbraco.Core // initializes a new instance of MainDom public MainDom(ILogger logger, IHostingEnvironment hostingEnvironment) { + HostingEnvironment.RegisterObject(this); + _logger = logger; - var appId = string.Empty; // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail - if (hostingEnvironment.ApplicationId != null) - appId = hostingEnvironment.ApplicationId.ReplaceNonAlphanumericChars(string.Empty); + var appId = hostingEnvironment.ApplicationId?.ReplaceNonAlphanumericChars(string.Empty); // combining with the physical path because if running on eg IIS Express, // two sites could have the same appId even though they are different. @@ -65,11 +66,11 @@ namespace Umbraco.Core // we *cannot* use the process ID here because when an AppPool restarts it is // a new process for the same application path - var appPath = hostingEnvironment.ApplicationPhysicalPath; + var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; var hash = (appId + ":::" + appPath).GenerateHash(); var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK"; - _asyncLock = new AsyncLock(lockName); + _systemLock = new SystemLock(lockName); var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT"; _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); @@ -100,6 +101,12 @@ namespace Umbraco.Core lock (_locko) { if (_signaled) return false; + if (_isMainDom == false) + { + _logger.Warn("Register called when MainDom has not been acquired"); + return false; + } + install?.Invoke(); if (release != null) _callbacks.Add(new KeyValuePair(weight, release)); @@ -119,64 +126,65 @@ namespace Umbraco.Core if (_signaled) return; if (_isMainDom == false) return; // probably not needed _signaled = true; - } - try - { - _logger.Info("Stopping ({SignalSource})", source); - foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + try { - try + _logger.Info("Stopping ({SignalSource})", source); + foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { - callback(); // no timeout on callbacks + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + _logger.Error(e, "Error while running callback"); + continue; + } } - catch (Exception e) - { - _logger.Error(e, "Error while running callback, remaining callbacks will not run."); - throw; - } - + _logger.Debug("Stopped ({SignalSource})", source); } - _logger.Debug("Stopped ({SignalSource})", source); - } - finally - { - // in any case... - _isMainDom = false; - _asyncLocker.Dispose(); - _logger.Info("Released ({SignalSource})", source); + finally + { + // in any case... + _isMainDom = false; + _systemLocker?.Dispose(); + _logger.Info("Released ({SignalSource})", source); + } + } } // acquires the main domain - internal bool Acquire() + private bool Acquire() { - lock (_locko) // we don't want the hosting environment to interfere by signaling + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment + if (_signaled) { - // if signaled, too late to acquire, give up - // the handler is not installed so that would be the hosting environment - if (_signaled) - { - _logger.Info("Cannot acquire (signaled)."); - return false; - } + _logger.Info("Cannot acquire (signaled)."); + return false; + } - _logger.Info("Acquiring."); + _logger.Info("Acquiring."); - // signal other instances that we want the lock, then wait one the lock, - // which may timeout, and this is accepted - see comments below + // signal other instances that we want the lock, then wait one the lock, + // which may timeout, and this is accepted - see comments below - // signal, then wait for the lock, then make sure the event is - // reset (maybe there was noone listening..) - _signal.Set(); + // signal, then wait for the lock, then make sure the event is + // reset (maybe there was noone listening..) + _signal.Set(); - // if more than 1 instance reach that point, one will get the lock - // and the other one will timeout, which is accepted - - //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset? - _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); - _isMainDom = true; + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted + //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. + try + { + _systemLocker = _systemLock.Lock(LockTimeoutMilliseconds); + } + finally + { // we need to reset the event, because otherwise we would end up // signaling ourselves and committing suicide immediately. // only 1 instance can reach that point, but other instances may @@ -184,35 +192,58 @@ namespace Umbraco.Core // which is accepted _signal.Reset(); - - //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread - - _signal.WaitOneAsync() - .ContinueWith(_ => OnSignal("signal")); - - HostingEnvironment.RegisterObject(this); - - _logger.Info("Acquired."); - return true; } + + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + + _signal.WaitOneAsync() + .ContinueWith(_ => OnSignal("signal")); + + _logger.Info("Acquired."); + return true; } /// /// Gets a value indicating whether the current domain is the main domain. /// - public bool IsMainDom => _isMainDom; + public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire()); // IRegisteredObject void IRegisteredObject.Stop(bool immediate) { - try + OnSignal("environment"); // will run once + + // The web app is stopping, need to wind down + Dispose(true); + + HostingEnvironment.UnregisterObject(this); + } + + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - OnSignal("environment"); // will run once - } - finally - { - HostingEnvironment.UnregisterObject(this); + if (disposing) + { + _signal?.Close(); + _signal?.Dispose(); + } + + disposedValue = true; } } + + public void Dispose() + { + Dispose(true); + } + + #endregion } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index a210f3c4a1..6dbb41093c 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -38,7 +38,8 @@ namespace Umbraco.Core.Runtime IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, IDbProviderFactoryCreator dbProviderFactoryCreator, - IBulkSqlInsertProvider bulkSqlInsertProvider) + IBulkSqlInsertProvider bulkSqlInsertProvider, + IMainDom mainDom) { IOHelper = ioHelper; Configs = configs; @@ -52,12 +53,14 @@ namespace Umbraco.Core.Runtime _umbracoBootPermissionChecker = umbracoBootPermissionChecker; Logger = logger; + MainDom = mainDom; + // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' // as the second one captures the current value (null) and therefore fails _state = new RuntimeState(Logger, Configs.Settings(), Configs.Global(), - new Lazy(() => _factory.GetInstance()), + new Lazy(() => mainDom), new Lazy(() => _factory.GetInstance()), UmbracoVersion,HostingEnvironment, BackOfficeInfo) { @@ -100,6 +103,8 @@ namespace Umbraco.Core.Runtime /// public IRuntimeState State => _state; + public IMainDom MainDom { get; private set; } + /// public virtual IFactory Boot(IRegister register) { @@ -179,15 +184,21 @@ namespace Umbraco.Core.Runtime Compose(composition); // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate - AcquireMainDom(mainDom); + AcquireMainDom(MainDom); // determine our runtime level DetermineRuntimeLevel(databaseFactory, ProfilingLogger); // get composers, and compose var composerTypes = ResolveComposerTypes(typeLoader); - composition.WithCollectionBuilder(); - var composers = new Composers(composition, composerTypes, ProfilingLogger); + + IEnumerable enableDisableAttributes; + using (ProfilingLogger.DebugDuration("Scanning enable/disable composer attributes")) + { + enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + } + + var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger); composers.Compose(); // create the factory @@ -196,6 +207,8 @@ namespace Umbraco.Core.Runtime // create & initialize the components _components = _factory.GetInstance(); _components.Initialize(); + + } catch (Exception e) { @@ -262,13 +275,13 @@ namespace Umbraco.Core.Runtime IOHelper.SetRootDirectory(path); } - private bool AcquireMainDom(MainDom mainDom) + private bool AcquireMainDom(IMainDom mainDom) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - return mainDom.Acquire(); + return mainDom.IsMainDom; } catch { diff --git a/src/Umbraco.Abstractions/AsyncLock.cs b/src/Umbraco.Core/SystemLock.cs similarity index 77% rename from src/Umbraco.Abstractions/AsyncLock.cs rename to src/Umbraco.Core/SystemLock.cs index 47868a4150..4eaae7082b 100644 --- a/src/Umbraco.Abstractions/AsyncLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -21,18 +21,18 @@ namespace Umbraco.Core // been closed, the Semaphore system object is destroyed - so in any case // an iisreset should clean up everything // - public class AsyncLock + internal class SystemLock { private readonly SemaphoreSlim _semaphore; private readonly Semaphore _semaphore2; private readonly IDisposable _releaser; private readonly Task _releaserTask; - public AsyncLock() - : this (null) + public SystemLock() + : this(null) { } - public AsyncLock(string name) + public SystemLock(string name) { // WaitOne() waits until count > 0 then decrements count // Release() increments count @@ -67,35 +67,6 @@ namespace Umbraco.Core : new NamedSemaphoreReleaser(_semaphore2); } - //NOTE: We don't use the "Async" part of this lock at all - //TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again - - //public Task LockAsync() - //{ - // var wait = _semaphore != null - // ? _semaphore.WaitAsync() - // : _semaphore2.WaitOneAsync(); - - // return wait.IsCompleted - // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - // : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), - // this, CancellationToken.None, - // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - //} - - //public Task LockAsync(int millisecondsTimeout) - //{ - // var wait = _semaphore != null - // ? _semaphore.WaitAsync(millisecondsTimeout) - // : _semaphore2.WaitOneAsync(millisecondsTimeout); - - // return wait.IsCompleted - // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - // : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), - // this, CancellationToken.None, - // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - //} - public IDisposable Lock() { if (_semaphore != null) @@ -121,14 +92,18 @@ namespace Umbraco.Core private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore _semaphore; - private GCHandle _handle; internal NamedSemaphoreReleaser(Semaphore semaphore) { _semaphore = semaphore; - _handle = GCHandle.Alloc(_semaphore); } + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue = false; // To detect redundant calls + public void Dispose() { Dispose(true); @@ -137,10 +112,22 @@ namespace Umbraco.Core private void Dispose(bool disposing) { - // critical - _handle.Free(); - _semaphore.Release(); - _semaphore.Dispose(); + if (!disposedValue) + { + try + { + _semaphore.Release(); + } + finally + { + try + { + _semaphore.Dispose(); + } + catch { } + } + disposedValue = true; + } } // we WANT to release the semaphore because it's a system object, ie a critical @@ -171,6 +158,9 @@ namespace Umbraco.Core // we do NOT want the finalizer to throw - never ever } } + + #endregion + } private class SemaphoreSlimReleaser : IDisposable diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7ff3544f7a..0c379eb45e 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -126,6 +126,7 @@ Constants.cs --> + diff --git a/src/Umbraco.Examine/MediaIndexPopulator.cs b/src/Umbraco.Examine/MediaIndexPopulator.cs index 6dadcbe4b3..1f5b11e54f 100644 --- a/src/Umbraco.Examine/MediaIndexPopulator.cs +++ b/src/Umbraco.Examine/MediaIndexPopulator.cs @@ -10,7 +10,7 @@ namespace Umbraco.Examine /// /// Performs the data lookups required to rebuild a media index /// - public class MediaIndexPopulator : IndexPopulator + public class MediaIndexPopulator : IndexPopulator { private readonly int? _parentId; private readonly IMediaService _mediaService; @@ -69,6 +69,6 @@ namespace Umbraco.Examine pageIndex++; } while (media.Length == pageSize); } - + } } diff --git a/src/Umbraco.Infrastructure/ContentExtensions.cs b/src/Umbraco.Infrastructure/ContentExtensions.cs index f74ecd5693..158e365958 100644 --- a/src/Umbraco.Infrastructure/ContentExtensions.cs +++ b/src/Umbraco.Infrastructure/ContentExtensions.cs @@ -8,6 +8,7 @@ using Umbraco.Composing; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index b702730a40..2bb9b404f4 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -192,6 +192,7 @@ namespace Umbraco.Core.Migrations.Upgrade // to 8.6.0 To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}"); + To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}"); //FINAL } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs new file mode 100644 index 0000000000..75de01dd7f --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_6_0/MissingContentVersionsIndexes.cs @@ -0,0 +1,24 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0 +{ + public class MissingContentVersionsIndexes : MigrationBase + { + public MissingContentVersionsIndexes(IMigrationContext context) : base(context) + { + } + + public override void Migrate() + { + Create + .Index("IX_" + ContentVersionDto.TableName + "_NodeId") + .OnTable(ContentVersionDto.TableName) + .OnColumn("nodeId") + .Ascending() + .OnColumn("current") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Models/MediaExtensions.cs b/src/Umbraco.Infrastructure/Models/MediaExtensions.cs index 1166698adb..08612d2810 100644 --- a/src/Umbraco.Infrastructure/Models/MediaExtensions.cs +++ b/src/Umbraco.Infrastructure/Models/MediaExtensions.cs @@ -1,10 +1,8 @@ -using System; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; -using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models { @@ -13,34 +11,17 @@ namespace Umbraco.Core.Models /// /// Gets the url of a media item. /// - public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger) + public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger, PropertyEditorCollection propertyEditors) { if (!media.Properties.TryGetValue(propertyAlias, out var property)) return string.Empty; - // TODO: would need to be adjusted to variations, when media become variants - if (!(property.GetValue() is string jsonString)) - return string.Empty; - - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField) - return jsonString; - - if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper) + if (propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - if (jsonString.DetectIsJson() == false) - return jsonString; - - try - { - var json = JsonConvert.DeserializeObject(jsonString); - if (json["src"] != null) - return json["src"].Value(); - } - catch (Exception ex) - { - logger.Error(ex, "Could not parse the string '{JsonString}' to a json object", jsonString); - return string.Empty; - } + // TODO: would need to be adjusted to variations, when media become variants + var value = property.GetValue(); + return dataEditor.GetMediaPath(value); } // Without knowing what it is, just adding a string here might not be very nice @@ -50,10 +31,10 @@ namespace Umbraco.Core.Models /// /// Gets the urls of a media item. /// - public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger) + public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger, PropertyEditorCollection propertyEditors) { return contentSection.ImageAutoFillProperties - .Select(field => media.GetUrl(field.Alias, logger)) + .Select(field => media.GetUrl(field.Alias, logger, propertyEditors)) .Where(link => string.IsNullOrWhiteSpace(link) == false) .ToArray(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs index 9b7a3ff001..f5292357e8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Persistence.Dtos [Column("nodeId")] [ForeignKey(typeof(ContentDto))] + [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current")] public int NodeId { get; set; } [Column("versionDate")] // TODO: db rename to 'updateDate' @@ -30,7 +31,6 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.Null)] public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero - // TODO: we need an index on this it is used almost always in querying and sorting [Column("current")] public bool Current { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs index f5dc2612f4..4ae859ba80 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Persistence.Factories { internal class ContentBaseFactory { - private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled); - /// /// Builds an IContent item from a dto and content type. /// @@ -187,7 +185,7 @@ namespace Umbraco.Core.Persistence.Factories /// /// Builds a dto from an IMedia item. /// - public static MediaDto BuildDto(IMedia entity) + public static MediaDto BuildDto(PropertyEditorCollection propertyEditors, IMedia entity) { var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Media); @@ -195,7 +193,7 @@ namespace Umbraco.Core.Persistence.Factories { NodeId = entity.Id, ContentDto = contentDto, - MediaVersionDto = BuildMediaVersionDto(entity, contentDto) + MediaVersionDto = BuildMediaVersionDto(propertyEditors, entity, contentDto) }; return dto; @@ -289,12 +287,20 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - private static MediaVersionDto BuildMediaVersionDto(IMedia entity, ContentDto contentDto) + private static MediaVersionDto BuildMediaVersionDto(PropertyEditorCollection propertyEditors, IMedia entity, ContentDto contentDto) { // try to get a path from the string being stored for media // TODO: only considering umbracoFile - TryMatch(entity.GetValue("umbracoFile"), out var path); + string path = null; + + if (entity.Properties.TryGetValue(Constants.Conventions.Media.File, out var property) + && propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) + { + var value = property.GetValue(); + path = dataEditor.GetMediaPath(value); + } var dto = new MediaVersionDto { @@ -306,22 +312,5 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - - // TODO: this should NOT be here?! - // more dark magic ;-( - internal static bool TryMatch(string text, out string path) - { - // In v8 we should allow exposing this via the property editor in a much nicer way so that the property editor - // can tell us directly what any URL is for a given property if it contains an asset - - path = null; - if (string.IsNullOrWhiteSpace(text)) return false; - - var m = MediaPathPattern.Match(text); - if (!m.Success || m.Groups.Count != 2) return false; - - path = m.Groups[1].Value; - return true; - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index a05e8cfe7f..3614a6bf45 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -101,7 +100,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ids.Any()) sql.WhereIn(x => x.NodeId, ids); - return MapDtosToContent(Database.Fetch(sql)); + return MapDtosToContent(Database.Fetch(sql), false, + // load everything + true, true, true, true); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -113,7 +114,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement AddGetByQueryOrderBy(sql); - return MapDtosToContent(Database.Fetch(sql)); + return MapDtosToContent(Database.Fetch(sql), false, + // load everything + true, true, true, true); } private void AddGetByQueryOrderBy(Sql sql) @@ -252,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .OrderByDescending(x => x.Current) .AndByDescending(x => x.VersionDate); - return MapDtosToContent(Database.Fetch(sql), true); + return MapDtosToContent(Database.Fetch(sql), true, true, true, true, true); } public override IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) @@ -262,7 +265,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .OrderByDescending(x => x.Current) .AndByDescending(x => x.VersionDate); - return MapDtosToContent(Database.Fetch(sql), true, true).Skip(skip).Take(take); + return MapDtosToContent(Database.Fetch(sql), true, + // load bare minimum + false, false, false, false).Skip(skip).Take(take); } public override IContent GetVersion(int versionId) @@ -862,7 +867,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } return GetPage(query, pageIndex, pageSize, out totalRecords, - x => MapDtosToContent(x), + x => MapDtosToContent(x, false, + // load properties but nothing else + true, false, false, true), filterSql, ordering); } @@ -949,7 +956,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ids.Length > 0) sql.WhereIn(x => x.UniqueId, ids); - return _outerRepo.MapDtosToContent(Database.Fetch(sql)); + return _outerRepo.MapDtosToContent(Database.Fetch(sql), false, + // load everything + true, true, true, true); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -1007,7 +1016,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement AddGetByQueryOrderBy(sql); - return MapDtosToContent(Database.Fetch(sql)); + return MapDtosToContent(Database.Fetch(sql), + // load the bare minimum + false, false, false, false, false); } /// @@ -1023,7 +1034,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement AddGetByQueryOrderBy(sql); - return MapDtosToContent(Database.Fetch(sql)); + return MapDtosToContent(Database.Fetch(sql), + // load the bare minimum + false, false, false, false, false); } #endregion @@ -1086,7 +1099,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return base.ApplySystemOrdering(ref sql, ordering); } - private IEnumerable MapDtosToContent(List dtos, bool withCache = false, bool slim = false) + private IEnumerable MapDtosToContent(List dtos, + bool withCache, + bool loadProperties, + bool loadTemplates, + bool loadSchedule, + bool loadVariants) { var temps = new List>(); var contentTypes = new Dictionary(); @@ -1119,7 +1137,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); - if (!slim) + if (loadTemplates) { // need templates var templateId = dto.DocumentVersionDto.TemplateId; @@ -1144,49 +1162,71 @@ namespace Umbraco.Core.Persistence.Repositories.Implement temps.Add(temp); } - if (!slim) + Dictionary templates = null; + if (loadTemplates) { // load all required templates in 1 query, and index - var templates = _templateRepository.GetMany(templateIds.ToArray()) + templates = _templateRepository.GetMany(templateIds.ToArray()) .ToDictionary(x => x.Id, x => x); + } + IDictionary properties = null; + if (loadProperties) + { // load all properties for all documents from database in 1 query - indexed by version id - var properties = GetPropertyCollections(temps); - var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray()); + properties = GetPropertyCollections(temps); + } - // assign templates and properties - foreach (var temp in temps) + var schedule = GetContentSchedule(temps.Select(x => x.Content.Id).ToArray()); + + // assign templates and properties + foreach (var temp in temps) + { + if (loadTemplates) { // set the template ID if it matches an existing template if (temp.Template1Id.HasValue && templates.ContainsKey(temp.Template1Id.Value)) temp.Content.TemplateId = temp.Template1Id; if (temp.Template2Id.HasValue && templates.ContainsKey(temp.Template2Id.Value)) temp.Content.PublishTemplateId = temp.Template2Id; + } - // set properties + + // set properties + if (loadProperties) + { if (properties.ContainsKey(temp.VersionId)) temp.Content.Properties = properties[temp.VersionId]; else throw new InvalidOperationException($"No property data found for version: '{temp.VersionId}'."); + } + if (loadSchedule) + { // load in the schedule if (schedule.TryGetValue(temp.Content.Id, out var s)) temp.Content.ContentSchedule = s; } + } - // set variations, if varying - temps = temps.Where(x => x.ContentType.VariesByCulture()).ToList(); - if (temps.Count > 0) + if (loadVariants) { - // load all variations for all documents from database, in one query - var contentVariations = GetContentVariations(temps); - var documentVariations = GetDocumentVariations(temps); - foreach (var temp in temps) - SetVariations(temp.Content, contentVariations, documentVariations); + // set variations, if varying + temps = temps.Where(x => x.ContentType.VariesByCulture()).ToList(); + if (temps.Count > 0) + { + // load all variations for all documents from database, in one query + var contentVariations = GetContentVariations(temps); + var documentVariations = GetDocumentVariations(temps); + foreach (var temp in temps) + SetVariations(temp.Content, contentVariations, documentVariations); + } } - foreach(var c in content) + + + foreach (var c in content) c.ResetDirtyProperties(false); // reset dirty initial properties (U4-1946) return content; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index a48012ac26..b0afc3b5f8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -239,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.SanitizeEntityPropertiesForXmlStorage(); // create the dto - var dto = ContentBaseFactory.BuildDto(entity); + var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity); // derive path and level from parent var parent = GetParentNodeDto(entity.ParentId); @@ -330,7 +330,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // create the dto - var dto = ContentBaseFactory.BuildDto(entity); + var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity); // update the node dto var nodeDto = dto.ContentDto.NodeDto; diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 7166d17c3f..8e200aacf8 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -6,7 +6,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Compose; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -16,8 +15,6 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; -[assembly:DisableComposer(typeof(Umbraco.Tests.Components.ComponentTests.Composer26))] - namespace Umbraco.Tests.Components { [TestFixture] @@ -73,10 +70,11 @@ namespace Umbraco.Tests.Components public void Boot1A() { var register = MockRegister(); + var typeLoader = MockTypeLoader(); var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - goes away with RuntimeLevel.Unknown @@ -115,7 +113,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run @@ -131,7 +129,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 21 is required by 20 // => reorder components accordingly @@ -146,7 +144,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // i23 requires 22 // 24, 25 implement i23 @@ -163,7 +161,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); try { @@ -186,7 +184,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = TypeArray(); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); // 2 is Core and requires 4 // 13 is required by 1 @@ -204,6 +202,7 @@ namespace Umbraco.Tests.Components Terminated.Clear(); var register = MockRegister(); + var typeLoader = MockTypeLoader(); var factory = MockFactory(m => { m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource()); @@ -221,7 +220,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Assert.IsEmpty(Composed); composers.Compose(); @@ -247,7 +246,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -262,7 +261,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), Configs, TestHelper.IOHelper, AppCaches.NoCache); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -275,11 +274,12 @@ namespace Umbraco.Tests.Components public void Requires2B() { var register = MockRegister(); + var typeLoader = MockTypeLoader(); var factory = MockFactory(); var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run), Configs, TestHelper.IOHelper, AppCaches.NoCache); var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); var builder = composition.WithCollectionBuilder(); @@ -298,32 +298,32 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), Configs, TestHelper.IOHelper, AppCaches.NoCache); var types = new[] { typeof(Composer10) }; - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); Assert.AreEqual(typeof(Composer10), Composed[0]); types = new[] { typeof(Composer11) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer2) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer12) }; - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(1, Composed.Count); @@ -337,7 +337,7 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), Configs, TestHelper.IOHelper, AppCaches.NoCache); var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); @@ -351,14 +351,15 @@ namespace Umbraco.Tests.Components var register = MockRegister(); var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown), Configs, TestHelper.IOHelper, AppCaches.NoCache); - var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute - var composers = new Composers(composition, types, Mock.Of()); + var types = new[] { typeof(Composer26) }; + var enableDisableAttributes = new[] { new DisableComposerAttribute(typeof(Composer26)) }; + var composers = new Composers(composition, types, enableDisableAttributes, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(0, Composed.Count); // 26 gone types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27 - composers = new Composers(composition, types, Mock.Of()); + composers = new Composers(composition, types, enableDisableAttributes, Mock.Of()); Composed.Clear(); composers.Compose(); Assert.AreEqual(2, Composed.Count); // both @@ -378,7 +379,7 @@ namespace Umbraco.Tests.Components MockRuntimeState(RuntimeLevel.Run), Configs, TestHelper.IOHelper, AppCaches.NoCache); var types = typeLoader.GetTypes().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")); - var composers = new Composers(composition, types, Mock.Of()); + var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(); var report = Composers.GetComposersReport(requirements); Console.WriteLine(report); @@ -517,7 +518,6 @@ namespace Umbraco.Tests.Components public class Composer25 : TestComposerBase, IComposer23 { } - // disabled by assembly attribute public class Composer26 : TestComposerBase { } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs index c0b9383b57..aa88f28dc0 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/SafeXmlReaderWriter.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return scopeProvider?.Context?.GetEnlisted(EnlistKey); } - public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, SystemLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) { var scopeContext = scopeProvider.Context; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs index 4da218cb48..d644239777 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStore.cs @@ -312,7 +312,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private XmlDocument _xmlDocument; // supplied xml document (for tests) private volatile XmlDocument _xml; // master xml document - private readonly AsyncLock _xmlLock = new AsyncLock(); // protects _xml + private readonly SystemLock _xmlLock = new SystemLock(); // protects _xml // to be used by PublishedContentCache only // for non-preview content only diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs index 145a19872a..56c09b18ac 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlStoreFilePersister.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache private bool _released; private Timer _timer; private DateTime _initialTouch; - private readonly AsyncLock _runLock = new AsyncLock(); // ensure we run once at a time + private readonly SystemLock _runLock = new SystemLock(); // ensure we run once at a time // note: // as long as the runner controls the runs, we know that we run once at a time, but diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 6489417dc7..8efc47cf11 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -4,13 +4,18 @@ using Moq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Services; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; using Umbraco.Web.Routing; namespace Umbraco.Tests.Routing @@ -25,7 +30,17 @@ namespace Umbraco.Tests.Routing { base.SetUp(); - _mediaUrlProvider = new DefaultMediaUrlProvider(); + var logger = Mock.Of(); + var mediaFileSystemMock = Mock.Of(); + var contentSection = Mock.Of(); + var dataTypeService = Mock.Of(); + + var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(new IDataEditor[] + { + new FileUploadPropertyEditor(logger, mediaFileSystemMock, contentSection, dataTypeService, LocalizationService), + new ImageCropperPropertyEditor(logger, mediaFileSystemMock, contentSection, dataTypeService, LocalizationService, IOHelper, ShortStringHelper, LocalizedTextService), + })); + _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors); } public override void TearDown() @@ -54,10 +69,10 @@ namespace Umbraco.Tests.Routing const string expected = "/media/rfeiw584/test.jpg"; var configuration = new ImageCropperConfiguration(); - var imageCropperValue = new ImageCropperValue + var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue { Src = expected - }; + }); var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider }); var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration); @@ -121,8 +136,8 @@ namespace Umbraco.Tests.Routing PropertyType = umbracoFilePropertyType, }; - property.SetValue("en", enMediaUrl, true); - property.SetValue("da", daMediaUrl); + property.SetSourceValue("en", enMediaUrl, true); + property.SetSourceValue("da", daMediaUrl); var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; @@ -131,7 +146,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(daMediaUrl, resolvedUrl); } - private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration) + private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration) { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); @@ -147,7 +162,7 @@ namespace Umbraco.Tests.Routing new SolidPublishedProperty { Alias = "umbracoFile", - SolidValue = propertyValue, + SolidSourceValue = propertyValue, SolidHasValue = true, PropertyType = umbracoFilePropertyType } diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index dccc658639..58e13583ca 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -5,6 +5,7 @@ using System.Web.Routing; using System.Web.Security; using Moq; using NUnit.Framework; +using NUnit.Framework.Internal; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; @@ -32,6 +33,7 @@ using Umbraco.Web.Runtime; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; using Umbraco.Web.Security.Providers; +using ILogger = Umbraco.Core.Logging.ILogger; namespace Umbraco.Tests.Routing { @@ -54,7 +56,7 @@ namespace Umbraco.Tests.Routing public class TestRuntime : WebRuntime { public TestRuntime(UmbracoApplicationBase umbracoApplication, Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - : base(umbracoApplication, configs, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider) + : base(umbracoApplication, configs, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider, TestHelper.MainDom) { } diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index e805c59b9e..64716754b0 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -122,7 +122,7 @@ namespace Umbraco.Tests.Runtimes public class TestRuntime : CoreRuntime { public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) - :base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider) + :base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider, TestHelper.MainDom) { } diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 64b45d41bf..63bcd87bd1 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -78,7 +78,7 @@ namespace Umbraco.Tests.Runtimes composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider); // create the core runtime and have it compose itself - var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider);coreRuntime.Compose(composition); + var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider, TestHelper.MainDom);coreRuntime.Compose(composition); // determine actual runtime level runtimeState.DetermineRuntimeLevel(databaseFactory, logger); @@ -89,7 +89,7 @@ namespace Umbraco.Tests.Runtimes var composerTypes = typeLoader.GetTypes() // all of them .Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components .Where(x => x != typeof(WebInitialComposer) && x != typeof(WebFinalComposer)); // exclude web runtime - var composers = new Composers(composition, composerTypes, profilingLogger); + var composers = new Composers(composition, composerTypes, Enumerable.Empty(), profilingLogger); composers.Compose(); // must registers stuff that WebRuntimeComponent would register otherwise @@ -273,7 +273,7 @@ namespace Umbraco.Tests.Runtimes composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider); // create the core runtime and have it compose itself - var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider); + var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.BulkSqlInsertProvider, TestHelper.MainDom); coreRuntime.Compose(composition); // get the components @@ -284,7 +284,7 @@ namespace Umbraco.Tests.Runtimes .Where(x => !x.FullName.StartsWith("Umbraco.Tests")); // single? //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; - var composers = new Composers(composition, composerTypes, profilingLogger); + var composers = new Composers(composition, composerTypes, Enumerable.Empty(), profilingLogger); // get components to compose themselves composers.Compose(); diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 27abca7cbd..5658434017 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Tests.TestHelpers; using Umbraco.Web.Scheduling; namespace Umbraco.Tests.Scheduling @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Scheduling [OneTimeSetUp] public void InitializeFixture() { - _logger = new DebugDiagnosticsLogger(new MessageTemplates()); + _logger = new ConsoleLogger(new MessageTemplates()); } [Test] @@ -102,12 +103,12 @@ namespace Umbraco.Tests.Scheduling { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { - MyTask t; + MyTask t1, t2, t3; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(t1 = new MyTask(5000)); + runner.Add(t2 = new MyTask()); + runner.Add(t3 = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks // shutdown -force => run all queued tasks @@ -115,7 +116,7 @@ namespace Umbraco.Tests.Scheduling Assert.IsTrue(runner.IsRunning); // is running tasks await runner.StoppedAwaitable; // runner stops, within test's timeout - Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run + Assert.AreNotEqual(DateTime.MinValue, t3.Ended); // t3 has run } } @@ -124,20 +125,25 @@ namespace Umbraco.Tests.Scheduling { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { - MyTask t; + MyTask t1, t2, t3; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(t1 = new MyTask(5000)); + runner.Add(t2 = new MyTask()); + runner.Add(t3 = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks + Thread.Sleep(1000); // since we are forcing shutdown, we need to give it a chance to start, else it will be canceled before the queue is started + // shutdown +force => tries to cancel the current task, ignores queued tasks runner.Shutdown(true, false); // +force -wait Assert.IsTrue(runner.IsRunning); // is running that long task it cannot cancel - await runner.StoppedAwaitable; // runner stops, within test's timeout - Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run + await runner.StoppedAwaitable; // runner stops, within test's timeout (no cancelation token used, no need to catch OperationCanceledException) + + Assert.AreNotEqual(DateTime.MinValue, t1.Ended); // t1 *has* run + Assert.AreEqual(DateTime.MinValue, t2.Ended); // t2 has *not* run + Assert.AreEqual(DateTime.MinValue, t3.Ended); // t3 has *not* run } } @@ -163,7 +169,15 @@ namespace Umbraco.Tests.Scheduling // shutdown +force => tries to cancel the current task, ignores queued tasks runner.Shutdown(true, false); // +force -wait - await runner.StoppedAwaitable; // runner stops, within test's timeout + try + { + await runner.StoppedAwaitable; // runner stops, within test's timeout ... maybe + } + catch (OperationCanceledException) + { + // catch exception, this can occur because we are +force shutting down which will + // cancel a pending task if the queue hasn't completed in time + } } } @@ -183,25 +197,20 @@ namespace Umbraco.Tests.Scheduling runner.Terminated += (sender, args) => { terminated = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(t = new MyTask()); // sleeps 500 ms ... total = 1500 ms until it's done Assert.IsTrue(runner.IsRunning); // is running the task - runner.Stop(false); // -immediate = -force, -wait + runner.Stop(false); // -immediate = -force, -wait (max 2000 ms delay before +immediate) + await runner.TerminatedAwaitable; + + Assert.IsTrue(stopped); // raised that one Assert.IsTrue(terminating); // has raised that event - Assert.IsFalse(terminated); // but not terminated yet + Assert.IsTrue(terminated); // and that event - // all this before we await because -wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner - Assert.IsTrue(runner.IsRunning); // still running the task - - await runner.StoppedAwaitable; // runner stops, within test's timeout - Assert.IsFalse(runner.IsRunning); - Assert.IsTrue(stopped); - - await runner.TerminatedAwaitable; // runner terminates, within test's timeout - Assert.IsTrue(terminated); // has raised that event + Assert.IsFalse(runner.IsRunning); // done running Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run } @@ -222,23 +231,21 @@ namespace Umbraco.Tests.Scheduling runner.Terminated += (sender, args) => { terminated = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false - runner.Add(new MyTask(5000)); - runner.Add(new MyTask()); - runner.Add(t = new MyTask()); + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(new MyTask()); // sleeps 500 ms + runner.Add(t = new MyTask()); // sleeps 500 ms ... total = 1500 ms until it's done Assert.IsTrue(runner.IsRunning); // is running the task - runner.Stop(true); // +immediate = +force, +wait + runner.Stop(true); // +immediate = +force, +wait (no delay) + await runner.TerminatedAwaitable; + + Assert.IsTrue(stopped); // raised that one Assert.IsTrue(terminating); // has raised that event Assert.IsTrue(terminated); // and that event - Assert.IsTrue(stopped); // and that one - // and all this before we await because +wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsFalse(runner.IsRunning); // done running - await runner.StoppedAwaitable; // runner stops, within test's timeout - await runner.TerminatedAwaitable; // runner terminates, within test's timeout - Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run } } @@ -264,8 +271,7 @@ namespace Umbraco.Tests.Scheduling }, _logger)) { Assert.IsTrue(runner.IsRunning); // because AutoStart is true - runner.Stop(false); // keepalive = must be stopped - await runner.StoppedAwaitable; // runner stops, within test's timeout + await runner.StopInternal(false); // keepalive = must be stopped } } @@ -291,13 +297,19 @@ namespace Umbraco.Tests.Scheduling // dispose will stop it } - await runner.StoppedAwaitable; // runner stops, within test's timeout - //await runner.TerminatedAwaitable; // NO! see note below + try + { + await runner.StoppedAwaitable; + } + catch (OperationCanceledException) + { + // swallow this exception, it can be expected to throw since when disposing we are calling Shutdown +force + // which depending on a timing operation may cancel the cancelation token + } + + Assert.Throws(() => runner.Add(new MyTask())); - // but do NOT await on TerminatedAwaitable - disposing just shuts the runner down - // so that we don't have a runaway task in tests, etc - but it does NOT terminate - // the runner - it really is NOT a nice way to end a runner - it's there for tests } [Test] @@ -564,7 +576,7 @@ namespace Umbraco.Tests.Scheduling Thread.Sleep(1000); Assert.IsTrue(runner.IsRunning); // still waiting for the task to release Assert.IsFalse(task.HasRun); - task.Release(); + task.Release(); // unlatch var runnerTask = runner.CurrentThreadingTask; // may be null if things go fast enough if (runnerTask != null) await runnerTask; // wait for current task to complete @@ -574,7 +586,7 @@ namespace Umbraco.Tests.Scheduling } [Test] - public async Task LatchedTaskStops() + public async Task LatchedTaskStops_Runs_On_Shutdown() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { @@ -584,7 +596,7 @@ namespace Umbraco.Tests.Scheduling Thread.Sleep(5000); Assert.IsTrue(runner.IsRunning); // still waiting for the task to release Assert.IsFalse(task.HasRun); - runner.Shutdown(false, false); + runner.Shutdown(false, false); // -force, -wait await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.IsTrue(task.HasRun); } @@ -880,7 +892,9 @@ namespace Umbraco.Tests.Scheduling public override void PerformRun() { + Console.WriteLine($"Sleeping {_milliseconds}..."); Thread.Sleep(_milliseconds); + Console.WriteLine("Wake up!"); } } @@ -997,7 +1011,9 @@ namespace Umbraco.Tests.Scheduling public DateTime Ended { get; set; } public virtual void Dispose() - { } + { + + } } } } diff --git a/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs b/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs index 3a3f8f369e..d856f3bd82 100644 --- a/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs +++ b/src/Umbraco.Tests/Services/ContentServicePublishBranchTests.cs @@ -200,7 +200,7 @@ namespace Umbraco.Tests.Services //update the child iv1.SetValue("vp", "UPDATED-iv1.de", "de"); - ServiceContext.ContentService.Save(iv1); + var saveResult = ServiceContext.ContentService.Save(iv1); var r = ServiceContext.ContentService.SaveAndPublishBranch(vRoot, false, "de").ToArray(); Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result); @@ -344,7 +344,7 @@ namespace Umbraco.Tests.Services ServiceContext.ContentService.Save(iv11); iv11.SetCultureName("iv11.ru", "ru"); - ServiceContext.ContentService.SaveAndPublish(iv11, new []{"de", "ru"}); + var xxx = ServiceContext.ContentService.SaveAndPublish(iv11, new []{"de", "ru"}); Assert.AreEqual("iv11.de", iv11.GetValue("vp", "de", published: true)); Assert.AreEqual("iv11.ru", iv11.GetValue("vp", "ru", published: true)); diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 17711fbd31..b3dc274c5e 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Services public void Can_Get_Media_With_Crop_By_Path() { var mediaService = ServiceContext.MediaService; - var mediaType = MockedContentTypes.CreateImageMediaType("Image2"); + var mediaType = MockedContentTypes.CreateImageMediaTypeWithCrop("Image2"); ServiceContext.MediaTypeService.Save(mediaType); var media = MockedMedia.CreateMediaImageWithCrop(mediaType, -1); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 35340940df..8bc1453e01 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -1,7 +1,9 @@ using System; using System.Linq; +using Moq; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Strings; namespace Umbraco.Tests.TestHelpers.Entities { @@ -420,10 +422,39 @@ namespace Umbraco.Tests.TestHelpers.Entities var contentCollection = new PropertyTypeCollection(false); contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); - contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + + mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); + + //ensure that nothing is marked as dirty + mediaType.ResetDirtyProperties(false); + + return mediaType; + } + + public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image) + { + var mediaType = new MediaType(TestHelper.ShortStringHelper, -1) + { + Alias = alias, + Name = "Image", + Description = "ContentType used for images", + Icon = ".sprTreeDoc3", + Thumbnail = "doc.png", + SortOrder = 1, + CreatorId = 0, + Trashed = false + }; + + var contentCollection = new PropertyTypeCollection(false); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.ImageCropper, ValueStorageType.Ntext) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = 1043 }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); + contentCollection.Add(new PropertyType(TestHelper.ShortStringHelper, Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId }); mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 }); diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs index e7b3f68c85..41ea230e7d 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedMedia.cs @@ -67,6 +67,8 @@ namespace Umbraco.Tests.TestHelpers.Entities media.SetValue(Constants.Conventions.Media.Bytes, "100"); media.SetValue(Constants.Conventions.Media.Extension, "png"); + + return media; } diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index ca10bbb9d0..c7ebb88185 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -92,7 +92,7 @@ namespace Umbraco.Tests.TestHelpers public static ICoreDebug CoreDebug { get; } = new CoreDebug(); public static IIOHelper IOHelper { get; } = new IOHelper(GetHostingEnvironment()); - + public static IMainDom MainDom { get; } = new MainDom(Mock.Of(), GetHostingEnvironment()); /// /// Maps the given making it rooted on . must start with ~/ /// diff --git a/src/Umbraco.Tests/Views/SWMaster.cshtml b/src/Umbraco.Tests/Views/SWMaster.cshtml new file mode 100644 index 0000000000..721baff9cf --- /dev/null +++ b/src/Umbraco.Tests/Views/SWMaster.cshtml @@ -0,0 +1,98 @@ +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; + var homepage = Model.Content.AncestorOrSelf(1); +} + + + + @Model.Content.GetPropertyValue("title") + + + + + + + + + + + + + + + + + + + +
+ @RenderBody() +
+ + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Tests/Views/textpage.cshtml b/src/Umbraco.Tests/Views/textpage.cshtml new file mode 100644 index 0000000000..d7b4dce307 --- /dev/null +++ b/src/Umbraco.Tests/Views/textpage.cshtml @@ -0,0 +1,4 @@ +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Views/web.config b/src/Umbraco.Tests/Views/web.config new file mode 100644 index 0000000000..efd80424e5 --- /dev/null +++ b/src/Umbraco.Tests/Views/web.config @@ -0,0 +1,74 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index ee63d0085e..59e8bf6c05 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -1,6 +1,14 @@ 'use strict'; module.exports = { + compile: { + build: { + sourcemaps: false + }, + dev: { + sourcemaps: true + } + }, sources: { // less files used by backoffice and preview diff --git a/src/Umbraco.Web.UI.Client/gulp/modes.js b/src/Umbraco.Web.UI.Client/gulp/modes.js new file mode 100644 index 0000000000..dc2947f2cc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/gulp/modes.js @@ -0,0 +1,13 @@ +'use strict'; + +var config = require('./config'); +var gulp = require('gulp'); + +function setDevelopmentMode(cb) { + + config.compile.current = config.compile.dev; + + return cb(); +}; + +module.exports = { setDevelopmentMode: setDevelopmentMode }; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js index 94150043c1..e33fc0389b 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js @@ -6,25 +6,36 @@ var postcss = require('gulp-postcss'); var less = require('gulp-less'); var autoprefixer = require('autoprefixer'); var cssnano = require('cssnano'); -var cleanCss = require("gulp-clean-css"); +var cleanCss = require('gulp-clean-css'); var rename = require('gulp-rename'); +var sourcemaps = require('gulp-sourcemaps'); module.exports = function(files, out) { - + var processors = [ autoprefixer, cssnano({zindex: false}) ]; - + console.log("LESS: ", files, " -> ", config.root + config.targets.css + out) - - var task = gulp.src(files) - .pipe(less()) - .pipe(cleanCss()) - .pipe(postcss(processors)) - .pipe(rename(out)) - .pipe(gulp.dest(config.root + config.targets.css)); - + + var task = gulp.src(files); + + if(config.compile.current.sourcemaps === true) { + task = task.pipe(sourcemaps.init()); + } + + task = task.pipe(less()); + task = task.pipe(cleanCss()); + task = task.pipe(postcss(processors)); + task = task.pipe(rename(out)); + + if(config.compile.current.sourcemaps === true) { + task = task.pipe(sourcemaps.write('./maps')); + } + + task = task.pipe(gulp.dest(config.root + config.targets.css)); + return task; - + }; diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 1e4dc591ca..705c54bf04 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -12,6 +12,8 @@ const { src, dest, series, parallel, lastRun } = require('gulp'); +const config = require('./gulp/config'); +const { setDevelopmentMode } = require('./gulp/modes'); const { dependencies } = require('./gulp/tasks/dependencies'); const { js } = require('./gulp/tasks/js'); const { less } = require('./gulp/tasks/less'); @@ -19,25 +21,14 @@ const { testE2e, testUnit } = require('./gulp/tasks/test'); const { views } = require('./gulp/tasks/views'); const { watchTask } = require('./gulp/tasks/watchTask'); -// Load local overwrites, can be used to overwrite paths in your local setup. -var fs = require('fs'); -var onlyScripts = require('./gulp/util/scriptFilter'); -try { - if (fs.existsSync('./gulp/overwrites/')) { - var overwrites = fs.readdirSync('./gulp/overwrites/').filter(onlyScripts); - overwrites.forEach(function(overwrite) { - require('./gulp/overwrites/' + overwrite); - }); - } -} catch (err) { - console.error(err) - } +// set default current compile mode: +config.compile.current = config.compile.build; // *********************************************************** // These Exports are the new way of defining Tasks in Gulp 4.x // *********************************************************** exports.build = series(parallel(dependencies, js, less, views), testUnit); -exports.dev = series(parallel(dependencies, js, less, views), watchTask); +exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask); exports.watch = series(watchTask); // exports.runTests = series(js, testUnit); diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 2652368819..4b3afbad18 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -862,6 +862,43 @@ } } }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", + "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "dev": true, + "requires": { + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.6.0", + "through2": "^2.0.3" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -3136,6 +3173,26 @@ "which": "^1.2.9" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3358,6 +3415,34 @@ "ms": "^2.1.1" } }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -3611,6 +3696,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -6601,6 +6692,39 @@ "through2": "^2.0.1" } }, + "gulp-sourcemaps": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", + "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "gulp-util": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", @@ -14750,6 +14874,12 @@ "strip-bom": "^2.0.0" } }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 44ecb4026f..0f02aba5e2 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -38,7 +38,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "14.0.2", + "nouislider": "14.1.1", "npm": "6.12.0", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", @@ -66,6 +66,7 @@ "gulp-postcss": "8.0.0", "gulp-rename": "1.4.0", "gulp-sort": "2.0.0", + "gulp-sourcemaps": "^2.6.5", "gulp-watch": "5.0.1", "gulp-wrap": "0.15.0", "gulp-wrap-js": "0.4.1", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js index b106f35efb..9117ab548c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js @@ -76,7 +76,6 @@ // Check if it is a new user const inviteVal = $location.search().invite; - vm.baseTitle = $scope.$root.locationTitle; //1 = enter password, 2 = password set, 3 = invalid token if (inviteVal && (inviteVal === "1" || inviteVal === "2")) { @@ -457,9 +456,7 @@ break; } - if (title != null) { - $scope.$root.locationTitle = title + " - " + vm.baseTitle; - } + $scope.$emit("$changeTitle", title); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 6559e16206..431a05778c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -224,7 +224,8 @@ Use this directive to construct a header inside the main editor window. if (editorState.current) { //to do make work for user create/edit // to do make it work for user group create/ edit - // to make it work for language edit/create + // to do make it work for language edit/create + // to do make it work for log viewer scope.isNew = editorState.current.id === 0 || editorState.current.id === "0" || editorState.current.id === -1 || diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js index 56dfb6b180..69ec1be805 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbautoresize.directive.js @@ -138,7 +138,11 @@ angular.module("umbraco.directives") var unbindModelWatcher = scope.$watch(function() { return ngModelController.$modelValue; }, function(newValue) { - update(true); + $timeout( + function() { + update(true); + } + ); }); scope.$on('$destroy', function() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js index eb3c134f0b..ff51b1ae90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbcheckbox.directive.js @@ -80,7 +80,8 @@ disabled: "<", required: "<", onChange: "&?", - cssClass: "@?" + cssClass: "@?", + iconClass: "@?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js new file mode 100644 index 0000000000..3b6a2c069a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/member/umbmembernodeinfo.directive.js @@ -0,0 +1,73 @@ +(function () { + 'use strict'; + + function MemberNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService) { + + function link(scope, element, attrs, ctrl) { + + var evts = []; + + //TODO: Infinite editing is not working yet. + scope.allowChangeMemberType = false; + + function onInit() { + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + } + + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL'); + scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL'); + }); + } + + scope.openMemberType = function (memberType) { + var editor = { + id: memberType.id, + submit: function (model) { + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.memberTypeEditor(editor); + }; + + // watch for content updates - reload content when node is saved, published etc. + scope.$watch('node.updateDate', function (newValue, oldValue) { + if (!newValue) { return; } + if (newValue === oldValue) { return; } + + // Update the create and update dates + formatDatesToLocal(); + }); + + //ensure to unregister from all events! + scope.$on('$destroy', function () { + for (var e in evts) { + eventsService.unsubscribe(evts[e]); + } + }); + + onInit(); + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/member/umb-member-node-info.html', + scope: { + node: "=" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMemberNodeInfo', MemberNodeInfoDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 31e797c6b4..9c33b35e82 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -16,9 +16,6 @@ angular.module("umbraco.directives") replace: true, templateUrl: 'views/components/property/umb-property.html', link: function (scope) { - - scope.propertyActions = []; - userService.getCurrentUser().then(function (u) { var isAdmin = u.userGroups.indexOf('admin') !== -1; scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null; @@ -36,6 +33,7 @@ angular.module("umbraco.directives") $scope.property.propertyErrorMessage = errorMsg; }; + $scope.propertyActions = []; self.setPropertyActions = function(actions) { $scope.propertyActions = actions; }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js index 32cbbb31ec..5eac7e5e24 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js @@ -15,7 +15,7 @@ function umbPropEditor(umbPropEditorHelper) { preview: "<" }, - require: "^^form", + require: ["^^form", "?^umbProperty"], restrict: 'E', replace: true, templateUrl: 'views/components/property/umb-property-editor.html', @@ -24,7 +24,10 @@ function umbPropEditor(umbPropEditorHelper) { //we need to copy the form controller val to our isolated scope so that //it get's carried down to the child scopes of this! //we'll also maintain the current form name. - scope[ctrl.$name] = ctrl; + scope[ctrl[0].$name] = ctrl[0]; + + // We will capture a reference to umbProperty in this Directive and pass it on to the Scope, so Property-Editor controllers can use it. + scope["umbProperty"] = ctrl[1]; if(!scope.model.alias){ scope.model.alias = Math.random().toString(36).slice(2); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js index 975b10d678..0a6eeb8835 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js @@ -90,7 +90,9 @@ angular.module("umbraco.directives") css.push("umb-tree-item--deleted"); } - if (actionNode) { + // checking the nodeType to ensure that this node and actionNode is from the same treeAlias + if (actionNode && actionNode.nodeType === node.nodeType) { + if (actionNode.id === node.id && String(node.id) !== "-1") { css.push("active"); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js index b0899f0f8b..d1fefeae5a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcheckmark.directive.js @@ -10,7 +10,8 @@ templateUrl: 'views/components/umb-checkmark.html', scope: { size: "@?", - checked: "=" + checked: "=", + readonly: "@?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 3c9e300f92..a9b9cc52b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -554,6 +554,7 @@ property.validation.patternMessage = propertyModel.validation.patternMessage; property.showOnMemberProfile = propertyModel.showOnMemberProfile; property.memberCanEdit = propertyModel.memberCanEdit; + property.isSensitiveData = propertyModel.isSensitiveData; property.isSensitiveValue = propertyModel.isSensitiveValue; property.allowCultureVariant = propertyModel.allowCultureVariant; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 3c64401933..8b922d7ec8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -359,6 +359,7 @@ When building a custom infinite editor view you can use the same components as a * * @param {Object} editor rendering options * @param {Boolean} editor.multiPicker Pick one or multiple items + * @param {Int} editor.startNodeId Set the startnode of the picker (optional) * @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object * @param {Function} editor.close Callback function when the close button is clicked. * @@ -564,6 +565,7 @@ When building a custom infinite editor view you can use the same components as a * @description * Opens a media picker in infinite editing, the submit callback returns an array of selected media items * @param {Object} editor rendering options + * @param {Int} editor.startNodeId Set the startnode of the picker (optional) * @param {Boolean} editor.multiPicker Pick one or multiple items * @param {Boolean} editor.onlyImages Only display files that have an image file-extension * @param {Boolean} editor.disableFolderSelect Disable folder selection @@ -608,8 +610,11 @@ When building a custom infinite editor view you can use the same components as a * @description * Opens the document type editor in infinite editing, the submit callback returns the alias of the saved document type. * @param {Object} editor rendering options - * @param {Callback} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. - * @param {Callback} editor.create Set to `true` to open the document type editor for creating a new document type. + * @param {Number} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. + * @param {Boolean} editor.create Set to `true` to open the document type editor for creating a new document type. + * @param {Boolean} editor.noTemplate If `true` and in combination with `create` being set to `true`, the document type editor will not create a corresponding template by default. This is similar to selecting the "Document Type without a template" in the Create dialog. + * @param {Boolean} editor.isElement If `true` and in combination with `create` being set to `true`, the "Is an Element type" option will be selected by default in the document type editor. + * @param {Boolean} editor.allowVaryByCulture If `true` and in combination with `create`, the "Allow varying by culture" option will be selected by default in the document type editor. * @param {Callback} editor.submit Submits the editor. * @param {Callback} editor.close Closes the editor. * @returns {Object} editor object @@ -636,6 +641,23 @@ When building a custom infinite editor view you can use the same components as a open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#memberTypeEditor + * @methodOf umbraco.services.editorService + * + * @description + * Opens the member type editor in infinite editing, the submit callback returns the saved member type + * @param {Object} editor rendering options + * @param {Callback} editor.submit Submits the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + function memberTypeEditor(editor) { + editor.view = "views/membertypes/edit.html"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#queryBuilder diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 8fe6761c94..41614a3bee 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -16,7 +16,7 @@ function fileManager($rootScope) { var mgr = { /** * @ngdoc function - * @name umbraco.services.fileManager#addFiles + * @name umbraco.services.fileManager#setFiles * @methodOf umbraco.services.fileManager * @function * 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 f66b0b0ed0..31375c5c56 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 @@ -1,323 +1,325 @@ -// This service was based on OpenJS library available in BSD License -// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php - -function keyboardService($window, $timeout) { - - var keyboardManagerService = {}; - - var defaultOpt = { - 'type': 'keydown', - 'propagate': false, - 'inputDisabled': false, - 'target': $window.document, - 'keyCode': false - }; - - // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken - var shift_nums = { - "`": "~", - "1": "!", - "2": "@", - "3": "#", - "4": "$", - "5": "%", - "6": "^", - "7": "&", - "8": "*", - "9": "(", - "0": ")", - "-": "_", - "=": "+", - ";": ":", - "'": "\"", - ",": "<", - ".": ">", - "/": "?", - "\\": "|" - }; - - // Special Keys - and their codes - var special_keys = { - 'esc': 27, - 'escape': 27, - 'tab': 9, - 'space': 32, - 'return': 13, - 'enter': 13, - 'backspace': 8, - - 'scrolllock': 145, - 'scroll_lock': 145, - 'scroll': 145, - 'capslock': 20, - 'caps_lock': 20, - 'caps': 20, - 'numlock': 144, - 'num_lock': 144, - 'num': 144, - - 'pause': 19, - 'break': 19, - - 'insert': 45, - 'home': 36, - 'delete': 46, - 'end': 35, - - 'pageup': 33, - 'page_up': 33, - 'pu': 33, - - 'pagedown': 34, - 'page_down': 34, - 'pd': 34, - - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - - 'f1': 112, - 'f2': 113, - 'f3': 114, - 'f4': 115, - 'f5': 116, - 'f6': 117, - 'f7': 118, - 'f8': 119, - 'f9': 120, - 'f10': 121, - 'f11': 122, - 'f12': 123 - }; - - var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; - - // The event handler for bound element events - function eventHandler(e) { - e = e || $window.event; - - var code, k; - - // Find out which key is pressed - if (e.keyCode) - { - code = e.keyCode; - } - else if (e.which) { - code = e.which; - } - - var character = String.fromCharCode(code).toLowerCase(); - - if (code === 188){character = ",";} // If the user presses , when the type is onkeydown - if (code === 190){character = ".";} // If the user presses , when the type is onkeydown - - var propagate = true; - - //Now we need to determine which shortcut this event is for, we'll do this by iterating over each - //registered shortcut to find the match. We use Find here so that the loop exits as soon - //as we've found the one we're looking for - _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) { - - var shortcutLabel = key; - var shortcutVal = keyboardManagerService.keyboardEvent[key]; - - // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked - var kp = 0; - - // Some modifiers key - var modifiers = { - shift: { - wanted: false, - pressed: e.shiftKey ? true : false - }, - ctrl: { - wanted: false, - pressed: e.ctrlKey ? true : false - }, - alt: { - wanted: false, - pressed: e.altKey ? true : false - }, - meta: { //Meta is Mac specific - wanted: false, - pressed: e.metaKey ? true : false - } - }; - - var keys = shortcutLabel.split("+"); - var opt = shortcutVal.opt; - var callback = shortcutVal.callback; - - // Foreach keys in label (split on +) - var l = keys.length; - for (var i = 0; i < l; i++) { - - var k = keys[i]; - switch (k) { - case 'ctrl': - case 'control': - kp++; - modifiers.ctrl.wanted = true; - break; - case 'shift': - case 'alt': - case 'meta': - kp++; - modifiers[k].wanted = true; - break; - } - - if (k.length > 1) { // If it is a special key - if (special_keys[k] === code) { - kp++; - } - } - else if (opt['keyCode']) { // If a specific key is set into the config - if (opt['keyCode'] === code) { - kp++; - } - } - else { // The special keys did not match - if (character === k) { - kp++; - } - else { - if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase - character = shift_nums[character]; - if (character === k) { - kp++; - } - } - } - } - - } //for end - - if (kp === keys.length && - modifiers.ctrl.pressed === modifiers.ctrl.wanted && - modifiers.shift.pressed === modifiers.shift.wanted && - modifiers.alt.pressed === modifiers.alt.wanted && - modifiers.meta.pressed === modifiers.meta.wanted) { - - //found the right callback! - - // Disable event handler when focus input and textarea - if (opt['inputDisabled']) { - var elt; - if (e.target) { - elt = e.target; - } else if (e.srcElement) { - elt = e.srcElement; - } - - if (elt.nodeType === 3) { elt = elt.parentNode; } - if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') { - //This exits the Find loop - return true; - } - } - - $timeout(function () { - callback(e); - }, 1); - - if (!opt['propagate']) { // Stop the event - propagate = false; - } - - //This exits the Find loop - return true; - } - - //we haven't found one so continue looking - return false; - - }); - - // Stop the event if required - if (!propagate) { - // e.cancelBubble is supported by IE - this will kill the bubbling process. - e.cancelBubble = true; - e.returnValue = false; - - // e.stopPropagation works in Firefox. - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - return false; - } - } - - // Store all keyboard combination shortcuts - keyboardManagerService.keyboardEvent = {}; - - // Add a new keyboard combination shortcut - keyboardManagerService.bind = function (label, callback, opt) { - - //replace ctrl key with meta key - if(isMac && label !== "ctrl+space"){ - label = label.replace("ctrl","meta"); - } - - var elt; - // Initialize opt object - opt = angular.extend({}, defaultOpt, opt); - label = label.toLowerCase(); - elt = opt.target; - if(typeof opt.target === 'string'){ - elt = document.getElementById(opt.target); - } - - //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding - // and raising events for now reason. So here we'll check if the event is already registered for the element - var boundValues = _.values(keyboardManagerService.keyboardEvent); - var found = _.find(boundValues, function (i) { - return i.target === elt && i.event === opt['type']; - }); - - // Store shortcut - keyboardManagerService.keyboardEvent[label] = { - 'callback': callback, - 'target': elt, - 'opt': opt - }; - - if (!found) { - //Attach the function with the event - if (elt.addEventListener) { - elt.addEventListener(opt['type'], eventHandler, false); - } else if (elt.attachEvent) { - elt.attachEvent('on' + opt['type'], eventHandler); - } else { - elt['on' + opt['type']] = eventHandler; - } - } - - }; - // Remove the shortcut - just specify the shortcut and I will remove the binding - keyboardManagerService.unbind = function (label) { - label = label.toLowerCase(); - var binding = keyboardManagerService.keyboardEvent[label]; - delete(keyboardManagerService.keyboardEvent[label]); - - if(!binding){return;} - - var type = binding['event'], - elt = binding['target'], - callback = binding['callback']; - - if(elt.detachEvent){ - elt.detachEvent('on' + type, callback); - }else if(elt.removeEventListener){ - elt.removeEventListener(type, callback, false); - }else{ - elt['on'+type] = false; - } - }; - // - - return keyboardManagerService; -} angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]); \ No newline at end of file +// This service was based on OpenJS library available in BSD License +// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php + +function keyboardService($window, $timeout) { + + var keyboardManagerService = {}; + + var defaultOpt = { + 'type': 'keydown', + 'propagate': false, + 'inputDisabled': false, + 'target': $window.document, + 'keyCode': false + }; + + // Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken + var shift_nums = { + "`": "~", + "1": "!", + "2": "@", + "3": "#", + "4": "$", + "5": "%", + "6": "^", + "7": "&", + "8": "*", + "9": "(", + "0": ")", + "-": "_", + "=": "+", + ";": ":", + "'": "\"", + ",": "<", + ".": ">", + "/": "?", + "\\": "|" + }; + + // Special Keys - and their codes + var special_keys = { + 'esc': 27, + 'escape': 27, + 'tab': 9, + 'space': 32, + 'return': 13, + 'enter': 13, + 'backspace': 8, + + 'scrolllock': 145, + 'scroll_lock': 145, + 'scroll': 145, + 'capslock': 20, + 'caps_lock': 20, + 'caps': 20, + 'numlock': 144, + 'num_lock': 144, + 'num': 144, + + 'pause': 19, + 'break': 19, + + 'insert': 45, + 'home': 36, + 'delete': 46, + 'end': 35, + + 'pageup': 33, + 'page_up': 33, + 'pu': 33, + + 'pagedown': 34, + 'page_down': 34, + 'pd': 34, + + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, + + 'f1': 112, + 'f2': 113, + 'f3': 114, + 'f4': 115, + 'f5': 116, + 'f6': 117, + 'f7': 118, + 'f8': 119, + 'f9': 120, + 'f10': 121, + 'f11': 122, + 'f12': 123 + }; + + var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + + // The event handler for bound element events + function eventHandler(e) { + e = e || $window.event; + + var code, k; + + // Find out which key is pressed + if (e.keyCode) + { + code = e.keyCode; + } + else if (e.which) { + code = e.which; + } + + var character = String.fromCharCode(code).toLowerCase(); + + if (code === 188){character = ",";} // If the user presses , when the type is onkeydown + if (code === 190){character = ".";} // If the user presses , when the type is onkeydown + + var propagate = true; + + //Now we need to determine which shortcut this event is for, we'll do this by iterating over each + //registered shortcut to find the match. We use Find here so that the loop exits as soon + //as we've found the one we're looking for + _.find(_.keys(keyboardManagerService.keyboardEvent), function(key) { + + var shortcutLabel = key; + var shortcutVal = keyboardManagerService.keyboardEvent[key]; + + // Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked + var kp = 0; + + // Some modifiers key + var modifiers = { + shift: { + wanted: false, + pressed: e.shiftKey ? true : false + }, + ctrl: { + wanted: false, + pressed: e.ctrlKey ? true : false + }, + alt: { + wanted: false, + pressed: e.altKey ? true : false + }, + meta: { //Meta is Mac specific + wanted: false, + pressed: e.metaKey ? true : false + } + }; + + var keys = shortcutLabel.split("+"); + var opt = shortcutVal.opt; + var callback = shortcutVal.callback; + + // Foreach keys in label (split on +) + var l = keys.length; + for (var i = 0; i < l; i++) { + + var k = keys[i]; + switch (k) { + case 'ctrl': + case 'control': + kp++; + modifiers.ctrl.wanted = true; + break; + case 'shift': + case 'alt': + case 'meta': + kp++; + modifiers[k].wanted = true; + break; + } + + if (k.length > 1) { // If it is a special key + if (special_keys[k] === code) { + kp++; + } + } + else if (opt['keyCode']) { // If a specific key is set into the config + if (opt['keyCode'] === code) { + kp++; + } + } + else { // The special keys did not match + if (character === k) { + kp++; + } + else { + if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase + character = shift_nums[character]; + if (character === k) { + kp++; + } + } + } + } + + } //for end + + if (kp === keys.length && + modifiers.ctrl.pressed === modifiers.ctrl.wanted && + modifiers.shift.pressed === modifiers.shift.wanted && + modifiers.alt.pressed === modifiers.alt.wanted && + modifiers.meta.pressed === modifiers.meta.wanted) { + + //found the right callback! + + // Disable event handler when focus input and textarea + if (opt['inputDisabled']) { + var elt; + if (e.target) { + elt = e.target; + } else if (e.srcElement) { + elt = e.srcElement; + } + + if (elt.nodeType === 3) { elt = elt.parentNode; } + if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA' || elt.hasAttribute('disable-hotkeys')) { + //This exits the Find loop + return true; + } + } + + $timeout(function () { + callback(e); + }, 1); + + if (!opt['propagate']) { // Stop the event + propagate = false; + } + + //This exits the Find loop + return true; + } + + //we haven't found one so continue looking + return false; + + }); + + // Stop the event if required + if (!propagate) { + // e.cancelBubble is supported by IE - this will kill the bubbling process. + e.cancelBubble = true; + e.returnValue = false; + + // e.stopPropagation works in Firefox. + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + } + } + + // Store all keyboard combination shortcuts + keyboardManagerService.keyboardEvent = {}; + + // Add a new keyboard combination shortcut + keyboardManagerService.bind = function (label, callback, opt) { + + //replace ctrl key with meta key + if(isMac && label !== "ctrl+space"){ + label = label.replace("ctrl","meta"); + } + + var elt; + // Initialize opt object + opt = angular.extend({}, defaultOpt, opt); + label = label.toLowerCase(); + elt = opt.target; + if(typeof opt.target === 'string'){ + elt = document.getElementById(opt.target); + } + + //Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding + // and raising events for now reason. So here we'll check if the event is already registered for the element + var boundValues = _.values(keyboardManagerService.keyboardEvent); + var found = _.find(boundValues, function (i) { + return i.target === elt && i.event === opt['type']; + }); + + // Store shortcut + keyboardManagerService.keyboardEvent[label] = { + 'callback': callback, + 'target': elt, + 'opt': opt + }; + + if (!found) { + //Attach the function with the event + if (elt.addEventListener) { + elt.addEventListener(opt['type'], eventHandler, false); + } else if (elt.attachEvent) { + elt.attachEvent('on' + opt['type'], eventHandler); + } else { + elt['on' + opt['type']] = eventHandler; + } + } + + }; + // Remove the shortcut - just specify the shortcut and I will remove the binding + keyboardManagerService.unbind = function (label) { + label = label.toLowerCase(); + var binding = keyboardManagerService.keyboardEvent[label]; + delete(keyboardManagerService.keyboardEvent[label]); + + if(!binding){return;} + + var type = binding['event'], + elt = binding['target'], + callback = binding['callback']; + + if(elt.detachEvent){ + elt.detachEvent('on' + type, callback); + }else if(elt.removeEventListener){ + elt.removeEventListener(type, callback, false); + }else{ + elt['on'+type] = false; + } + }; + // + + return keyboardManagerService; +} + +angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html index 6b71f1db9e..fc870858cf 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.html @@ -15,11 +15,11 @@
-

Great!, no need to configure anything then, you simply click the continue button below to continue to the next step

+

Great! No need to configure anything, you can simply click the continue button below to continue to the next step

- What is the exact connectionstring we should use? + What is the exact connection string we should use?
@@ -64,7 +64,7 @@
Enter the database user name
diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html index ad9b1c9cc1..0c78e211dc 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/permissionsreport.html @@ -1,8 +1,8 @@ 
-

Your permission settings are not ready for umbraco

+

Your permission settings are not ready for Umbraco

- In order to run umbraco, you'll need to update your permission settings. - Detailed information about the correct file & folder permissions for Umbraco can be found + In order to run Umbraco, you'll need to update your permission settings. + Detailed information about the correct file and folder permissions for Umbraco can be found here.

diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html b/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html index df1e58d737..f30788e858 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/version7upgradereport.html @@ -2,11 +2,11 @@

Major version upgrade from {{installer.current.model.currentVersion}} to {{installer.current.model.newVersion}}

There were {{installer.current.model.errors.length}} issues detected

- The following compatibility issues were found. If you continue all non-compatible property editors will be converted to a Readonly/Label. + The following compatibility issues were found. If you continue, all non-compatible property editors will be converted to a Readonly/Label. You will be able to change the property editor to a compatible type manually by editing the data type after installation.

- Otherwise if you choose not to proceed you will need to fix the errors listed below. + Otherwise, if you choose not to proceed, you will need to fix the errors listed below. Refer to v{{installer.current.model.newVersion}} upgrade instructions for full details.

@@ -19,4 +19,4 @@

-
\ No newline at end of file +
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 391fafb3fa..f6490fc79b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -2,6 +2,7 @@ // Core variables and mixins @import "fonts.less"; // Loading fonts @import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "colors.less"; // Colors from variables but as specific CSS classes @import "mixins.less"; // CSS Reset @@ -74,9 +75,8 @@ @import "sections.less"; @import "helveticons.less"; @import "main.less"; -@import "listview.less"; +@import "listview.less"; @import "gridview.less"; -@import "footer.less"; @import "filter-toggle.less"; @import "forms/umb-validation-label.less"; @@ -206,7 +206,6 @@ @import "utilities/_cursor.less"; //page specific styles -@import "pages/document-type-editor.less"; @import "pages/login.less"; @import "pages/welcome-dashboard.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index b36c73a61a..7135692ae8 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -1,5 +1,7 @@ @import "helveticons.less"; +@import "variables.less"; +@import "application/umb-outline.less"; /******* font-face *******/ @@ -13,730 +15,197 @@ /****************************/ body { - overflow: hidden; - height: 100%; - width: 100%; - width: calc(~"100% - 80px"); // 80px is the fixed left menu for toggling the different browser sizes position: absolute; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + height: calc(~"100% - 40px"); + width: 100%; padding: 0; margin: 0; - font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - color: #343434; - transition: all 0.2s ease-in-out; - padding-left:80px; + padding-bottom:40px; + background-color: @brownGrayExtraLight; } -h4 { - margin: 0; - font-size: 16px; - background-color: #535353; - color: #b3b3b3; - text-transform: uppercase; - position: initial; - display: block; - padding: 5px 10px; - font-size: 12px; - font-weight: normal; -} - -h5 { - margin: 0 0 6px 0; - font-size: 12px; -} - -ul { - list-style:none; - margin:0; - padding:0; -} - -a, a:hover{ - color:#333; - text-decoration:none; -} - - /****************************/ /* General class */ /****************************/ -.right { - float:right; - display:inline-block; -} - -.left { - float:left; - display:inline-block; -} - -.leftOpen { - padding-left: 330px -} - -.wait { - display: block; - height: 100%; - width: 100%; - background:#fff center center url(../img/loader.gif) no-repeat; -} - -/****************************/ -/* Group button */ -/****************************/ - -.btn-group { - float: right; - margin-right: 10px; -} - -.btn { - display: inline-block; - padding: 4px 12px; - margin-bottom: 0; - font-size: 14px; - line-height: 20px; - color: #000000; - text-align: center; - vertical-align: middle; - cursor: pointer; - background: #f2f2f2; - border: 1px solid #cccccc; - border-radius: 2px; - box-shadow: none; -} - -.btn-group > .btn + .dropdown-toggle { - box-shadow: none; -} - -.btn-group > .btn + .btn { - margin-left: -6px; -} - -.btn-group > .btn + .dropdown-toggle { - padding-right: 8px; - padding-left: 8px; - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group > .btn:last-child, .btn-group > .dropdown-toggle { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid #FFFFFF; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; - border-top: 0; - border-bottom: 4px solid #ffffff; - margin-top: 8px; - margin-left: 0; -} - -.dropdown-menu { +.menu-bar { position: absolute; - display: block; - top: auto; - right: 0; - z-index: 1000; - display: block; - float: left; - min-width: 160px; - padding: 5px 0; - margin: -96px 10px 0 0; - margin-bottom: 1px; - list-style: none; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 6px; - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - background-clip: padding-box; -} - -.dropdown-menu > li > a, -.dropdown-menu > li > button { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 20px; - color: black; - white-space: nowrap; - cursor:pointer; -} - -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus, -.dropdown-menu > li > button:hover, -.dropdown-menu > li > button:focus, -.dropdown-submenu:hover > a, -.dropdown-submenu:focus > a { - color: #000000; - background: #e4e0dd; -} - -/****************************/ -/* Speech bubble */ -/****************************/ - -#speechbubble { - position: absolute; - right: 0; - bottom: 40px; + bottom:0; left: 0; - z-index: 9999; - display: none; - padding: 0; - margin: auto; - margin-left: 300px; - text-align: left; - background: none; - border: none; - border-bottom: none; -} + right: 0; + background-color: @blueExtraDark; + color:@white; -#speechbubble p { - position: relative; - padding: 8px 30px 8px 20px; - margin: auto; - margin-top: 5px; - font-size: 12px; - color: #ffffff; - text-shadow: none; - background-color: #46a546; - border: none; - border-color: transparent; - border-radius: 5px 0 0 5px; -} - - -/****************************/ -/* Main section menu */ -/****************************/ - -.more-options i { - display: inline-block; - width: 5px !important; - height: 5px !important; - margin: 5px 1px 7px 0; - background: #d9d9d9; - border-radius: 20px; -} - -.fix-left-menu { - position: fixed; - top: 0; - left: 0; - width: 80px; - height: 100%; - padding: 0; - margin-left: -80px; font-family: "Lato", Helvetica, Arial, sans-serif; - font-size: 13px; + font-size: 12px; line-height: 16px; - background: #1b264f; - transition: all 0.2s ease-in-out; - z-index: 9999; + + animation: menu-bar-animation 1.2s; + animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); + } -.avatar { - text-align:center; - padding: 27px 0 29px 0; - border-bottom: 1px solid #2E2246; -} +@keyframes menu-bar-animation { + 0% { + bottom: -50px; + } + 40% { + bottom: -50px; + } + 80% { + bottom: 0px; + } + } -.help { - position: absolute; - bottom: 0; - left: 0; - display: block; - width: 100%; - margin: 0; - font-size: 30px; - text-align: center; - color: #D8D7D9; - opacity: 0.4; - transition: all .3s linear; -} +.menu-bar__right-part { + float: right; + display: flex; + flex: row; -ul.sections { - display: block; - background: #1b264f; - position:absolute; - top: 90px; - width: 80px; - transition: all 0.2s ease-in-out; - list-style:none; - margin:0; - padding:0; - margin-left: -80px; - overflow: auto; - overflow-x: hidden; - height: calc(100% - 91px); - - &::-webkit-scrollbar { - width: 0px; - background: transparent; + > div, > button { + border-left: 1px solid rgba(255, 255, 255, .25); } } -ul.sections li { - display: block; - border-left: 4px #1b264f solid; - transition: all .3s linear; +.menu-bar__title { + display: inline-block; + padding: 11px 15px; + font-weight: bold; + font-size: 13px; +} + +.menu-bar__button { + display: inline-block; + padding: 11px 15px; + height: 40px; + border:none; + background-color: @blueExtraDark; + + text-align: left; + font: inherit; + color: inherit; cursor: pointer; -} -.fix-left-menu ul.sections li a span, -.fix-left-menu ul.sections li a i { - color: #fff; - opacity: .7; - transition: all .3s linear; -} + transition: color 120ms linear, background-color 120ms linear; + + .icon { + margin-right: 10px; + font-size: 18px; + vertical-align: middle; + } + + span { + vertical-align: middle; + } + + > svg { + display: inline-block; + width: 14px; + height: 14px; + fill: #fff; + margin-right: 10px; + vertical-align: middle; + transition: fill 120ms linear; + } -ul.sections li a { - display: block; - width: 100%; - height: 100%; - padding: 20px 4px 15px 0; - margin: 0 0 0 -4px; - text-align: center; - text-decoration: none; - border-bottom: 1px solid #2E2246; &:hover { - span, i { - opacity: 1; - color:#fff; + background-color: lighten(@blueExtraDark, 4%); + } + &.--active { + color: @pinkLight; + > svg { + fill: @pinkLight; } } } -ul.sections li a i { - font-size: 30px; - opacity: 0.8; -} +.preview-menu-option { -ul.sections li a span { - display: block; - font-size: 10px; - line-height: 1.4em; - opacity: 0.8; -} - -ul.sections li.current { - border-left: 4px #f5c1bc solid; -} - -ul.sections li.current a i { - color: #f5c1bc; -} - -ul.sections li.current { - border-left: 4px #f5c1bc solid; -} - -ul.sections li:hover a i, -ul.sections li:hover a span { - opacity: 1; -} - -.fix-left-menu:hover .help { - opacity: 1; -} - -.fix-left-menu.selected, -.sections.selected { - margin-left:0px; -} - -/*************************************************/ -/* Main panel */ -/*************************************************/ - -.main-panel { - position: fixed; - top: 0; - left: 0; - margin-left: -330px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 16px; - background: #ffffff; - transition: all 0.2s ease-in-out; - width: 250px; - height: 100%; - padding: 0; - z-index: 999; - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); -} - -.main-panel .header { - padding: 28px 20px 32px 20px; - font-weight: bold; - background: #f8f8f8; - border-bottom: 1px solid #d9d9d9; -} - -.main-panel .header h3 { - color: rgba(179, 179, 179, 0.49); - font-size: 24px; - margin:0; -} - -.main-panel .header h3 i { - position: absolute; - right: 20px; - cursor:pointer; - transition: all 0.2s ease-in-out; -} - -.main-panel .header h3 i:hover { - color:#333; -} - -.main-panel.selected { - margin-left: 80px; - position: absolute; - right: 0; - bottom: 0; - left: 0; - overflow: auto; -} - -.main-panel .content { - padding:20px 0; -} - -/*************************************************/ -/* float-panel */ -/*************************************************/ - -.float-panel { - position: fixed; - top: 0; - z-index: 99; - width: 250px; - height: 100%; - padding: 0; - padding: 20px; - margin-left: -480px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 16px; - background: #ffffff; - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - transition: all 0.2s ease-in-out; -} - -.float-panel.selected{ - margin-left: 0px; -} - -/*************************************************/ -/* sample palette color */ -/*************************************************/ - -.samples h4 { - position: initial; - display: block; - padding: 5px 10px; - margin: 0; - border: 0px solid #6B6B6B; - font-weight: normal; - font-size: 12px; -} - -.samples > li { - padding: 6px 20px; - cursor: pointer; -} - -.samples > li:hover, .samples > li.hover { - background: #f8f8f8; -} - -.samples > li ul { - display:table; - width:100%; -} - -.samples > li ul > li { - display: table-cell; - height: 15px; - padding: 0; -} - -/*************************************************/ -/* canvas designer panel */ -/*************************************************/ - -h4.panel-title { - cursor:pointer; - background-color: #f8f8f8; - color: #767676; -} - -.editor-category{ - margin: 5px 10px; - border: 1px solid #D9D9D9; -} - -.canvasdesigner-panel-container { - padding: 10px 10px; -} - -.canvasdesigner-panel-property { - clear: both; - overflow: hidden; - margin: 0 0 10px 0; -} - -.canvasdesigner .box-slider { - padding: 0px 0px 6px 0px; - overflow: hidden; - clear: both; -} - -.field-title { - float: left; - margin-right: 10px; - font-size: 12px; - color: #d9d9d9; -} - -.div-field { - margin-bottom: 10px; - overflow: hidden; - clear: both; -} - -/*************************************************/ -/* font family picker */ -/*************************************************/ - -.fontFamilyPickerPreview { - float: left; - width: 90%; - padding: 8px; - margin-top: 4px; - clear: both; - font-size: 18px; - color: #CDCDCD; - cursor: pointer; - border: 1px solid #CDCDCD; - text-align: center; position: relative; -} - -.fontFamilyPickerPreview span { - font-size: 32px; - line-height: 32px; -} - -.fontPickerDelete { - position: absolute; - margin: 5px 0 0 -15px; - cursor: pointer; - color:#CDCDCD; - right: 0; - top: 0; -} - -.fontFamilyPickerPreview:hover { - border: 1px solid #979797; - color:#979797; -} - -.fontFamilyPickerPreview:hover .fontPickerDelete { - color:#979797; -} - -.canvasdesigner-fontfamilypicker { - margin-bottom:30px; -} - -.canvasdesigner-fontfamilypicker select { - font-size: 12px; - padding: 2px; -} - -.canvasdesigner-fontfamilypicker select.font-list { - width:170px; -} - -.canvasdesigner-fontfamilypicker select.variant-list { - width:60px; -} - -.canvasdesigner-fontfamilypicker { - font-size: 42px; - line-height: 50px; - margin-bottom:40px; -} - -/*************************************************/ -/* slider */ -/*************************************************/ - -.canvasdesigner .ui-widget-content { - background: rgba(0, 0, 0, 0.27) !important; - border: 0 solid #fff !important; - border-radius: 1px !important; -} - -.canvasdesigner .ui-slider-horizontal { - margin: 14px 11px 0 11px; -} - -.ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default, -.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, -.ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { - border: 1px solid #535353 !important; - background: #535353 !important; - background-color:none !important; - outline:none; -} - -.canvasdesigner .ui-slider .ui-slider-handle:hover, -.canvasdesigner .ui-slider .ui-slider-handle:focus { - color: #d9d9d9 !important; -} - -.canvasdesigner .ui-slider .ui-slider-handle span { - position: absolute !important; - margin: -15px 0 0 -8px !important; - color: #535353 !important; - font-size: 9px !important; - text-align: -webkit-center !important; - display: block !important; - width: 30px !important; -} - -.canvasdesigner .slider-input { - float: right; - width: 25px; - padding: 0; - margin-top: -9px; - margin-right: 1px; - font-size: 12px; - color: #d9d9d9; - text-align: right; - background-color: transparent; - border: none; - -} - -@-moz-document url-prefix() { - .canvasdesigner .slider-input { - margin-top: -6px; - } -} - -.canvasdesigner .sp-replacer { - padding: 0; - margin: 0; - display: block; - border: none; - height: 26px; - border-radius: 1px; - border: 1px solid #CDCDCD; -} - -.canvasdesigner .sp-replacer:hover { - border: 1px solid #979797; -} - -.canvasdesigner .panel-body { - border-top: none !important; -} - -.canvasdesigner select { - font-size: 12px; -} - -.canvasdesigner .sp-dd { - display: none; -} - -.canvasdesigner .sp-preview { - width: 100%; - height: 100%; - margin-right: 0; - border: none; - display: block; -} - -.canvasdesigner .color-picker-preview { - height: 26px; - border: 1px solid #CDCDCD; - border-radius: 1px; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); - cursor: pointer; -} - -.canvasdesigner .float-panel .sp-container.sp-flat { - background-color: transparent; - border: none; - padding: 10px; - width: 100%; -} - -.canvasdesigner .float-panel .sp-container.sp-flat .sp-picker-container { - padding: 0; - margin: 0px; - margin-bottom: 10px; - border: none; - width: 100%; -} - -.canvasdesigner .float-panel .sp-container.sp-flat .sp-palette-container { - padding: 0; - height: 32px; - width: 100%; - float: left; - margin: 0; - border: none; -} - -.canvasdesigner .float-panel .sp-container.sp-flat .sp-button-container { - display: none; -} - -.colorPickerDelete { - position: absolute; - margin: -23px 0 0 0; - cursor: pointer; -} - -.borderStyleSelect -{ display: inline-block; - float: right; - width: 75px; - height: 28px; + + > .menu-bar__button { + position: relative; + } + + .dropdown-menu { + display:none; + + position: absolute; + right: 0; + bottom: 100%; + min-width: 200px; + + border-radius: 3px 3px 0 3px; + overflow: hidden; + + background-color: @blueExtraDark; + + > button { + position: relative; + display: list-item; + text-align: left; + width: 100%; + + &.--active { + &::before { + content: ''; + position: absolute; + left:0; + width: 3px; + top: 0; + bottom: 0; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + background-color: @pinkLight; + } + } + } + } + + &.--open { + z-index:1; + box-shadow: 0 5px 10px 0 rgba(0,0,0,.26); + > .menu-bar__button { + z-index: @zindexDropdown + 1; + } + .dropdown-menu { + display:block; + z-index: @zindexDropdown; + box-shadow: 0 5px 10px 0 rgba(0,0,0,.26); + } + } + } + + + /*************************************************/ /* IFrame size */ /*************************************************/ -.desktop { +#demo-iframe-wrapper { + transition: all 240ms cubic-bezier(0.165, 0.84, 0.44, 1); +} + +.fullsize { width: 100%; height: 100%; margin: 0 auto; overflow: hidden; } +.desktop { + width: 1920px; + height: 1080px; +} + .laptop { width: 1366px; height: 768px; @@ -762,13 +231,13 @@ h4.panel-title { height: 360px; } -.border { - margin: 75px auto; - background-color: #ffffff; - border-radius: 10px; +.shadow { + margin: 10px auto; + background-color: @white; + border-radius: 3px; + overflow: hidden; opacity: 1.0; - box-shadow: 0 0 0 29px #E9E9EB, 0 0 0 30px #D8D7D9; - transition: all 0.5s ease-in-out; + box-shadow: 0 5px 20px 0 rgba(0,0,0,.26); } iframe { @@ -786,251 +255,3 @@ iframe { .flip:before { transform: rotate(90deg); } - -/*************************************************/ -/* Image picker */ -/*************************************************/ - -.imagePickerPreview { - height: 20px; - text-align: center; - background-color: #fff; - padding: 6px 0 0 0; - cursor: pointer; - background-size: cover; - border-radius:1px; - border: 1px solid #CDCDCD; - color: #CDCDCD; -} - -.sp-clear-display { - background-image: none !important; -} - -.imagePickerPreview:hover { - border: 1px solid #979797; -} - -.imagePickerPreview:hover i { - color: #979797; -} - -.imagePickerPreview i { - font-size:24px; - color: #CDCDCD; -} - -.canvasdesignerImagePicker { - padding: 0; - margin-top: 10px; - overflow: auto; - text-align: left; - list-style: none; -} - -.canvasdesignerImagePicker ul { - padding: 0; - margin: 0; - margin-left: 20px; - list-style: none; -} - -.canvasdesignerImagePicker li { - display: inline-block; - margin-bottom: 10px; -} - -.canvasdesignerImagePicker ul.media-items li { - margin-right: 20px; -} - -.canvasdesignerImagePicker .media-preview { - position: relative; - display: inline-block; - width: 91px; - height: 78px; - cursor: pointer; - background-position: center center; - background-size: cover; - border: 2px solid #F3F2F2; -} - -.canvasdesignerImagePicker .media-preview .folder { - position: absolute; - top: 30px; - left: 0; - width: 100%; - font-size: 40px; - color: gainsboro; - text-align: center; -} - -.canvasdesignerImagePicker .media-preview .folder-name { - margin-top: -5px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 0; - color: grey; -} - -.canvasdesignerImagePicker .media-preview:hover, -.media-preview.selected { - border: 2px solid #84B8F0; -} - -.canvasdesignerImagePicker .modal-dialog { - width: 618px; - font-weight: normal; -} - -.bodyCanvasdesignerImagePicker .breadcrumb { - margin-top: 4px; - margin-bottom: 10px; - font-size: 16px; - text-align: left; -} - -.bodyCanvasdesignerImagePicker .breadcrumb > li { - display: inline; -} - -.breadcrumb{ - font-size: 12px; -} - -.bodyCanvasdesignerImagePicker .breadcrumb > li a { - cursor: pointer; -} - -.bodyCanvasdesignerImagePicker .breadcrumb input { - padding: 0; - margin: -4px -4px; -} - -.bodyCanvasdesignerImagePicker .fileinput-button { - position: absolute; - top: 10px; - right: 10px; - font-size: 19px; -} - -.bodyCanvasdesignerImagePicker input.input-media { - position: absolute; - top: 0; - right: 0; - margin: 0; - font-size: 23px; - cursor: pointer; - opacity: 0; - transform: translate(-300px, 0) scale(4); - direction: ltr; -} - -.bodyCanvasdesignerImagePicker .breadcrumb a.disabled, -.bodyCanvasdesignerImagePicker .breadcrumb a.disabled:hover { - color: grey; - text-decoration: none; - cursor: default; -} - -.canvasdesignerImagePicker h3 { - font-size: 18px; - color: #555555; -} - -/*************************************************/ -/* Border editor */ -/*************************************************/ - -.box-preview { - display:inline-block; -} - -.box-preview li { - height: 30px; - width: 30px; - border-radius: 1px; - border: none; - display:inline-block; - background-color:#535353; - cursor:pointer; - margin-right: 6px; - position:relative; -} - -.box-preview li:last-child{ - margin-right: 0px; -} - -.box-preview li.selected { - border-color:#53a93f !important; -} - -.box-preview li.border-all { - border: 6px solid #b3b3b3; - height: 18px; - width: 18px; - margin-left:0px -} - -.box-preview li.border-left { - border-left: 6px solid #b3b3b3; - width: 24px; -} - -.box-preview li.border-right { - border-right: 6px solid #b3b3b3; - width: 24px; -} - -.box-preview li.border-top { - border-top: 6px solid #b3b3b3; - height: 24px; -} - -.box-preview li.border-bottom { - border-bottom: 6px solid #b3b3b3; - height: 24px; -} - -.bordereditor .color-picker-preview { - display: inline-block; - width: 120px; - float: left; -} - -/*************************************************/ -/* Radius editor */ -/*************************************************/ - -.radius-top-left, .radius-top-right, .radius-bottom-left, .radius-bottom-right { - display: block; - position: absolute; - background-color: #b3b3b3; - width: 7px; - height: 7px; -} - -.box-preview li.selected span { - background-color:#53a93f; -} - -.radius-top-left{ - top:0; - left:0; -} - -.radius-top-right{ - top:0; - right:0; -} - -.radius-bottom-left{ - bottom:0; - left:0; -} - -.radius-bottom-right{ - bottom:0; - right:0 -} diff --git a/src/Umbraco.Web.UI.Client/src/less/colors.less b/src/Umbraco.Web.UI.Client/src/less/colors.less new file mode 100644 index 0000000000..4cb70cdf5e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/colors.less @@ -0,0 +1,81 @@ +.red{color: @red;} +.blue{color: @blue;} +.black{color: @black;} +.turquoise{color: @turquoise;} +.turquoise-d1{color: @turquoise-d1;} + +.text-warning { + color: @orange; +} +.text-error { + color: @red; +} +.text-success { + color: @green; +} + + +//icon colors for tree icons +.color-red, .color-red i{color: @red-d1 !important;} +.color-blue, .color-blue i{color: @turquoise-d1 !important;} +.color-orange, .color-orange i{color: @orange !important;} +.color-green, .color-green i{color: @green-d1 !important;} +.color-yellow, .color-yellow i{color: @yellowIcon !important;} + +/* Colors based on https://zavoloklom.github.io/material-design-color-palette/colors.html */ +.btn-color-black {background-color: @black;} +.color-black i { color: @black;} + +.btn-color-blue-grey {background-color: @blueGrey;} +.color-blue-grey, .color-blue-grey i { color: @blueGrey !important;} + +.btn-color-grey{background-color: @grayIcon;} +.color-grey, .color-grey i { color: @grayIcon !important; } + +.btn-color-brown{background-color: @brownIcon;} +.color-brown, .color-brown i { color: @brownIcon !important; } + +.btn-color-blue{background-color: @blueIcon;} +.color-blue, .color-blue i { color: @blueIcon !important; } + +.btn-color-light-blue{background-color: @lightBlueIcon;} +.color-light-blue, .color-light-blue i {color: @lightBlueIcon !important;} + +.btn-color-cyan{background-color: @cyanIcon;} +.color-cyan, .color-cyan i { color: @cyanIcon !important; } + +.btn-color-green{background-color: @greenIcon;} +.color-green, .color-green i { color: @greenIcon !important; } + +.btn-color-light-green{background-color: @lightGreenIcon;} +.color-light-green, .color-light-green i {color: @lightGreenIcon !important; } + +.btn-color-lime{background-color: @limeIcon;} +.color-lime, .color-lime i { color: @limeIcon !important; } + +.btn-color-yellow{background-color: @yellowIcon;} +.color-yellow, .color-yellow i { color: @yellowIcon !important; } + +.btn-color-amber{background-color: @amberIcon;} +.color-amber, .color-amber i { color: @amberIcon !important; } + +.btn-color-orange{background-color: @orangeIcon;} +.color-orange, .color-orange i { color: @orangeIcon !important; } + +.btn-color-deep-orange{background-color: @deepOrangeIcon;} +.color-deep-orange, .color-deep-orange i { color: @deepOrangeIcon !important; } + +.btn-color-red{background-color: @redIcon;} +.color-red, .color-red i { color: @redIcon !important; } + +.btn-color-pink{background-color: @pinkIcon;} +.color-pink, .color-pink i { color: @pinkIcon !important; } + +.btn-color-purple{background-color: @purpleIcon;} +.color-purple, .color-purple i { color: @purpleIcon !important; } + +.btn-color-deep-purple{background-color: @deepPurpleIcon;} +.color-deep-purple, .color-deep-purple i { color: @deepPurpleIcon !important; } + +.btn-color-indigo{background-color: @indigoIcon;} +.color-indigo, .color-indigo i { color: @indigoIcon !important; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less index a621370d02..456601a7bd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-toggle.less @@ -57,7 +57,7 @@ .umb-toggle.umb-toggle--checked & { transform: translateX(20px); - background-color: white; + background-color: @white; } } @@ -75,7 +75,7 @@ .umb-toggle__icon--left { left: 5px; - color: white; + color:@white; transition: opacity 120ms; opacity: 0; // .umb-toggle:hover & { @@ -85,7 +85,7 @@ opacity: 1; } .umb-toggle.umb-toggle--checked:hover & { - color: white; + color:@white; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 8324698685..ed80359833 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -5,7 +5,7 @@ .umb-card{ position: relative; padding: 5px 10px 5px 10px; - background: white; + background: @white; width: 100%; .title{padding: 12px; color: @gray-3; border-bottom: 1px solid @gray-8; font-weight: 400; font-size: 16px; text-transform: none; margin: 0 -10px 10px -10px;} @@ -86,7 +86,7 @@ margin: 0 auto; list-style: none; width: 100%; - + display: flex; flex-flow: row wrap; justify-content: flex-start; @@ -119,7 +119,7 @@ padding-top: 100%; border-radius: 3px; transition: background-color 120ms; - + > span { position: absolute; top: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 44cd86a189..1217441f4e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -17,8 +17,8 @@ } } .umb-editor-sub-header--white { - background-color: white; - border-color: white; + background-color: @white; + border-color: @white; } .umb-editor-sub-header.--state-selection { @@ -33,11 +33,11 @@ transition: box-shadow 240ms; position:sticky; z-index: 30; - + &.umb-sticky-bar--active { box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); } - + .umb-dashboard__content & { top:-20px; // umb-dashboard__content has 20px padding - offset here prevents sticky position from firing when page loads } @@ -45,8 +45,8 @@ .umb-sticky-sentinel { pointer-events: none; - z-index: 5050; - + z-index: 5050; + &.-top { height:1px; transform:translateY(-10px); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index 6859280680..d2a3bdedb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -1,6 +1,6 @@ .umb-editors { .absolute(); - overflow: hidden; + overflow: hidden; .umb-editor { box-shadow: 0px 0 30px 0 rgba(0,0,0,.3); @@ -16,7 +16,7 @@ transform: none; will-change: transform; transition: transform 400ms ease-in-out; - + &.umb-editor--moveRight { transform: translateX(110%); } @@ -28,7 +28,7 @@ will-change: auto; transition: display 0s 320ms; } - + &--level0 { transform: none; } @@ -43,11 +43,11 @@ .umb-editor--level@{i} { transform: translateX(@x); } - + .umb-editor--n@{i} { right:@x; } - + .level-loop(@i - 1); } @@ -62,18 +62,18 @@ .umb-editor { @size: extract(extract(@editorSizes, @iterator), 1); @value: extract(extract(@editorSizes, @iterator), 2); - + &--@{size} { width: @value; will-change: transform; left: auto; - + .umb-editor--container { max-width: @value; } } } - + .create-editor-sizes(@iterator + 1); } @@ -94,3 +94,14 @@ opacity: 1; transition: opacity 320ms 20ms, visibility 0s; } + +.umb-editor--trashed-message { + background:@errorBackground; + color:@errorText; + padding:10px; + margin-bottom:20px; + + i { + margin-right:5px; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 609cf0af3d..eb8740b385 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -66,7 +66,7 @@ flex-shrink: 1; flex-basis: auto; position: relative; - padding: 20px; + padding: 30px; background: @white; max-height: calc(100vh - 170px); overflow-y: auto; @@ -117,7 +117,7 @@ .umb-overlay.umb-overlay-center .umb-overlay-drawer { border: none; background: transparent; - padding: 0 20px 20px; + padding: 0 30px 20px; } /* ---------- OVERLAY TARGET ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less index 9947c793c2..7036d60a63 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -44,7 +44,7 @@ display: flex; padding: 6px; margin: 10px 0px !important; - background: #F3F3F5; + background: @gray-10; cursor: move; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less index 8945d15ec6..df01477880 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/tree/umb-tree-item.less @@ -3,11 +3,11 @@ min-width: 100%; width: auto; margin-top:1px; - + .umb-tree-item__label { user-select: none; } - + &:hover .umb-tree-item__arrow { visibility: visible; cursor: pointer @@ -36,7 +36,7 @@ overflow: hidden; margin-right: 6px; } - + // Loading Animation // ------------------------ .umb-tree-item__loader { @@ -46,7 +46,7 @@ } .umb-tree-item__label { - padding: 7px 0 5px; + padding: 7px 0 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -71,7 +71,7 @@ left: 0; right: 0; bottom: 0; - border: 2px solid fade(white, 80%); + border: 2px solid fade(@white, 80%); } &:hover { @@ -86,12 +86,12 @@ .umb-tree-item.current > .umb-tree-item__inner { background: @ui-active; color:@ui-active-type; - - // override small icon color. TODO => check usage + + // override small icon color. TODO => check usage &:before { color: @blue; } - + .umb-options { &:hover i { @@ -113,5 +113,5 @@ .umb-tree-item.current-not-active > .umb-tree-item__inner { background: @ui-active-blur; - color:@ui-active-type; + color:@ui-active-type; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less index 21f4a7bda8..c6b9dc7261 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-avatar.less @@ -103,7 +103,7 @@ a.umb-avatar-btn:hover { text-decoration: none; } a.umb-avatar-btn .umb-avatar { - border: 2px dashed #A2A1A6; + border: 2px dashed @gray-6; } a.umb-avatar-btn .umb-avatar span { font-size: 50px; @@ -114,4 +114,4 @@ a.umb-avatar-btn .umb-avatar span { font-size: 50px; } -/*border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: #F3F3F5; border: 2px dashed #A2A1A6; color: #A2A1A6;*/ +/*border-radius: 50%; width: 100px; height: 100px; font-size: 50px; text-align: center; display: flex; align-items: center; justify-content: center; background-color: @gray-10; border: 2px dashed @gray-6; color: @gray-6;*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less index 3c63d74a47..0afcfdd1f9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-breadcrumbs.less @@ -8,7 +8,7 @@ .umb-breadcrumbs__ancestor { display: flex; - min-height: 25px; + align-items: center; } .umb-breadcrumbs__action { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index 7f19c4933c..76a4df0056 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -6,7 +6,7 @@ flex-wrap: wrap; align-items: center; position: relative; - padding: 0 !important; + padding: 0 0 0 26px !important; margin: 0; min-height: 22px; line-height: 22px; @@ -21,7 +21,6 @@ } &__text { - margin: 0 0 0 26px; position: relative; top: 1px; user-select: none; @@ -80,7 +79,7 @@ outline: 2px solid @inputBorderTabFocus; } .tabbing-active &.umb-form-check--checkbox &__input:checked:focus ~ .umb-form-check__state .umb-form-check__check { - border-color: white; + border-color: @white; } // add spacing between when flexed/inline, equal to the width of the input diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less index 277c2bcbe8..479074fee9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less @@ -437,7 +437,7 @@ .umb-grid .umb-row.-active-child { background-color: @gray-10; - + .umb-row-title-bar { cursor: inherit; } @@ -445,7 +445,7 @@ .umb-row-title { color: @gray-3; } - + } @@ -582,10 +582,10 @@ .umb-grid .iconBox.selected { -webkit-appearance: none; - background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); + background-image: linear-gradient(to bottom,@gray-9,@gray-7); background-repeat: repeat-x; zoom: 1; - border-color: #bfbfbf #bfbfbf #999; + border-color: @gray-7 @gray-7 @gray-6; border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); border-radius: 3px; @@ -638,9 +638,9 @@ .umb-grid .mce-toolbar { border-bottom: 1px solid @gray-7; - background-color: white; + background-color: @white; display: none; - + left: 0; right: 0; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less index 34070256ce..e8a62f739d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less @@ -51,7 +51,7 @@ // Color swatch .button { border: none; - color: white; + color: @white; padding: 5px; text-align: center; text-decoration: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less index a87e7084fb..f3b53f4def 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-insert-code-box.less @@ -11,7 +11,7 @@ cursor: pointer; } -.umb-insert-code-box:hover, +.umb-insert-code-box:hover, .umb-insert-code-box.-selected { background-color: @ui-option-hover; color: @ui-action-type-hover; @@ -32,7 +32,7 @@ .umb-insert-code-box__check { width: 18px; height: 18px; - background: @gray-10;x + background: @gray-10; border-radius: 50%; display: flex; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less index cdc6cfcb63..9ebd6d6e5d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -23,10 +23,10 @@ .umb-layout-selector__dropdown { position: absolute; padding: 5px; - background: #333; + background: @grayDark; z-index: 999; display: flex; - background: #fff; + background: @white; flex-wrap: wrap; flex-direction: column; transform: translate(-50%,0); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less index f6dfed63c1..ba46c68a57 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list-view-settings.less @@ -52,7 +52,7 @@ tbody tr { background: @gray-10; - border-bottom: 1px solid #fff; + border-bottom: 1px solid @white; } th { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less index 76223589e4..8beff55b7c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -83,7 +83,7 @@ top: 0; line-height: 32px; right: 140px; - color: #fdb45c; + color: @yellow-d1; cursor: pointer; } @@ -92,7 +92,7 @@ top: 0; line-height: 32px; right: 120px; - color: #bbbabf; + color: @gray-7; cursor: pointer; } @@ -133,7 +133,7 @@ } .exception { - border-left: 4px solid #D42054; + border-left: 4px solid @red; padding: 0 10px 10px 10px; box-shadow: rgba(0,0,0,0.07) 2px 2px 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 50244c2079..4feadc272c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -42,6 +42,10 @@ .umb-media-grid__item.-selectable { cursor: pointer; + + .tabbing-active &:focus { + outline: 2px solid @inputBorderTabFocus; + } } .umb-media-grid__item.-file { @@ -54,7 +58,7 @@ color: @ui-selected-type; } } -.umb-media-grid__item.-selected, +.umb-media-grid__item.-selected, .umb-media-grid__item.-selectable:hover { &::before { content: ""; @@ -118,6 +122,7 @@ .umb-media-grid__item-overlay { display: flex; + width: 100%; opacity: 0; position: absolute; right: 0; @@ -130,13 +135,17 @@ overflow: hidden; color: @black; white-space: nowrap; - border-top:1px solid fade(black, 4%); + border-top:1px solid fade(@black, 4%); background: fade(@white, 92%); transition: opacity 150ms; - + &:hover { text-decoration: underline; } + + .tabbing-active &:focus { + opacity: 1; + } } .umb-media-grid__info { @@ -181,7 +190,7 @@ align-items: center; color: @black; transition: opacity 150ms; - + &:hover { color: @ui-action-discreet-type-hover; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less index 7a2939bd30..8cd08f5045 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-mini-list-view.less @@ -13,9 +13,15 @@ margin-right: 5px; } +.umb-mini-list-view__breadcrumb { + .flex; + margin-bottom: 10px; + min-height: 25px; +} + .umb-mini-list-view__back { - font-size: 12px; - margin-right: 5px; + font-size: 13px; + margin-right: 5px; color: @gray-4; display: flex; align-items: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index bf0dd9d109..699496f5d3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -82,7 +82,7 @@ padding: 15px 5px; color:@ui-option-type; border-radius: 3px 3px 0 0; - + &:hover { color:@ui-option-type-hover; } @@ -104,7 +104,7 @@ padding-left: 30px; } } - + } .umb-nested-content__icons { @@ -226,19 +226,19 @@ display: none !important; } -.umb-nested-content__doctypepicker table input, +.umb-nested-content__doctypepicker table input, .umb-nested-content__doctypepicker table select { width: 100%; padding-right: 0; } -.umb-nested-content__doctypepicker table td.icon-navigation, +.umb-nested-content__doctypepicker table td.icon-navigation, .umb-nested-content__doctypepicker i.umb-nested-content__help-icon { vertical-align: middle; color: @gray-7; } -.umb-nested-content__doctypepicker table td.icon-navigation:hover, +.umb-nested-content__doctypepicker table td.icon-navigation:hover, .umb-nested-content__doctypepicker i.umb-nested-content__help-icon:hover { color: @gray-2; } @@ -248,11 +248,11 @@ } .umb-nested-content__placeholder { - padding: 4px 6px; - border: 1px dashed #d8d7d9; + padding: 4px 6px; + border: 1px dashed @gray-8; background: 0 0; cursor: pointer; - color: #1b264f; + color: @blueExtraDark; -webkit-animation: fadeIn .5s; animation: fadeIn .5s; text-align: center; @@ -265,8 +265,8 @@ } .umb-nested-content__placeholder:hover { - color: #2152a3; - border-color: #2152a3; + color: @blueMid; + border-color: @blueMid; text-decoration: none; } @@ -288,7 +288,7 @@ // the attribute selector ensures the change only applies to the linkpicker overlay .form-horizontal .umb-nested-content--narrow [ng-controller*="Umbraco.Overlays.LinkPickerController"] .controls-row { margin-left:0!important; - + .umb-textarea, .umb-textstring { width:100%; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less index 16457787a3..b808e6574a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -67,6 +67,7 @@ height: 100%; width: 100%; border-radius: 3px; + border: 0 none; text-decoration: none !important; transition: border-color 100ms ease; background-color: @white; @@ -193,9 +194,8 @@ color: @black; box-sizing: border-box; justify-content: center; - border-top: 1px solid @gray-8; - border-bottom: 1px solid @gray-8; - border-right: 1px solid @gray-8; + border: 1px solid @gray-8; + border-left: 0; padding: 10px 0; background: @white; } @@ -411,6 +411,7 @@ } .umb-gallery__thumbnail { + background: transparent; flex: 0 1 100px; border: 1px solid @ui-action-discreet-border; border-radius: 3px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less index 3ce284870e..3f0b981ac6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less @@ -39,7 +39,7 @@ top: -15px; border-radius: 3px 3px 0 0; - + border-top-left-radius: 3px; border-top-right-radius: 3px; @@ -49,8 +49,8 @@ .box-shadow(0 5px 20px rgba(0,0,0,.3)); - background-color: white; - + background-color: @white; + } .umb-property .umb-property-actions { @@ -71,12 +71,12 @@ } .umb-property-actions__menu { - + position: absolute; z-index: 1000; display: block; - + float: left; min-width: 160px; list-style: none; @@ -85,11 +85,11 @@ border-top-left-radius: 0; margin-top:1px; - + } .umb-contextmenu-item > button { - + z-index:2;// need to stay on top of menu-toggle-open shadow. } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less index 1461d0f223..6ae92ffa4e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-range-slider.less @@ -1,7 +1,7 @@ .umb-range-slider.noUi-target { - background: linear-gradient(to bottom, #f5f5f5 0%, #f9f9f9 100%); + background: linear-gradient(to bottom, @grayLighter 0%, @grayLighter 100%); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); border-radius: 20px; height: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less index 7caec3c78e..a612af65ef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-details.less @@ -1,7 +1,7 @@ .umb-user-details-avatar { margin-bottom: 20px; padding-bottom: 20px; - border-bottom: 1px solid #d8d7d9; + border-bottom: 1px solid @gray-8; } div.umb-user-details-actions > div { @@ -81,7 +81,7 @@ a.umb-user-details-details__back-link { margin-bottom: 30px; margin-right: 0; } - + .umb-user-details-details__sidebar { flex: 1 1 auto; width: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less index 807d0e863f..d64ffef098 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/getstarted.less @@ -22,8 +22,8 @@ text-align: center; display: flex; align-items: center; - border: 1px solid #d8d7d9; - background-color: #fff; + border: 1px solid @gray-8; + background-color: @white; margin: 0 0 0.5em; @media (min-width: 500px) { diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less index 4ebe1d47b0..91922300fb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less @@ -4,7 +4,7 @@ } .top-border { - border-top: 2px solid #f3f3f5; + border-top: 2px solid @gray-10; } .no-left-padding { diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less index b51bfeffa9..21ec047d41 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/umbraco-forms.less @@ -106,7 +106,7 @@ .step-text { font-size: 16px; line-height: 1.5; - color: #4c4c4c; + color: @gray; margin-bottom: 20px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/footer.less b/src/Umbraco.Web.UI.Client/src/less/footer.less deleted file mode 100644 index 93fdb6ab5e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/less/footer.less +++ /dev/null @@ -1,2 +0,0 @@ -// Footer -// ------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less index bb684cd69b..238feead90 100644 --- a/src/Umbraco.Web.UI.Client/src/less/gridview.less +++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less @@ -365,12 +365,12 @@ .usky-grid .iconBox.selected { -webkit-appearance: none; - background-image: -webkit-gradient(linear,0 0,0 100%,from(#e6e6e6),to(#bfbfbf)); - background-image: -webkit-linear-gradient(top,#e6e6e6,#bfbfbf); - background-image: linear-gradient(to bottom,#e6e6e6,#bfbfbf); + background-image: -webkit-gradient(linear,0 0,0 100%,from(@gray-9),to(@gray-7)); + background-image: -webkit-linear-gradient(top,@gray-9,@gray-7); + background-image: linear-gradient(to bottom,@gray-9,@gray-7); background-repeat: repeat-x; zoom: 1; - border-color: #bfbfbf #bfbfbf #999; + border-color: @gray-7 @gray-7 @gray-6; border-color: rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25); box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05); border-radius: 3px; @@ -379,7 +379,7 @@ .usky-grid .iconBox i { font-size:16px !important; - color: #5F5F5F; + color: @gray-4; display:block; } diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 865f015ffa..e964ed3c6f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -1,6 +1,7 @@ // Core variables and mixins @import "fonts.less"; // Loading fonts @import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "colors.less"; @import "mixins.less"; @import "buttons.less"; @import "forms.less"; @@ -133,8 +134,8 @@ legend { } input.ng-dirty.ng-invalid { - border-color: #b94a48; - color: #b94a48; + border-color: @pink; + color: @pink; } .disabled { diff --git a/src/Umbraco.Web.UI.Client/src/less/legacydialog.less b/src/Umbraco.Web.UI.Client/src/less/legacydialog.less index ca920d22c2..f1835a682c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/legacydialog.less +++ b/src/Umbraco.Web.UI.Client/src/less/legacydialog.less @@ -16,8 +16,8 @@ margin-top: 10px; height: 100%; overflow: auto; - border-top: 1px solid #ccc; - border-top: 1px solid #ccc; + border-top: 1px solid @gray-8; + border-top: 1px solid @gray-8; padding: 5px; } @@ -30,11 +30,11 @@ .umb-dialog .diff table th { padding: 5px; width: 25%; - border-bottom: 1px solid #ccc; + border-bottom: 1px solid @gray-8; } .umb-dialog .diff table td { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid @gray-8; padding: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 86a1acbeae..0d646d11c6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -183,6 +183,17 @@ h5.-black { .umb-control-group .umb-el-wrap { padding: 0; } +.umb-control-group .control-header { + + .control-label { + float: left; + } + + .control-description { + display: block; + clear: both; + } +} .form-horizontal .umb-control-group .control-header { float: left; width: 160px; @@ -190,15 +201,12 @@ h5.-black { text-align: left; .control-label { - float: left; width: auto; padding-top: 0; text-align: left; } .control-description { - display: block; - clear: both; max-width:480px;// avoiding description becoming too wide when its placed on top of property. margin-bottom: 10px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index e49755338b..21b9c5c550 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -332,7 +332,7 @@ } // Gradient Bar Colors for buttons and alerts -.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff) { +.gradientBar(@primaryColor, @secondaryColor, @textColor: @white) { color: @textColor; #gradient > .vertical(@primaryColor, @secondaryColor); border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); @@ -341,7 +341,7 @@ // Gradients #gradient { - .horizontal(@startColor: #555, @endColor: #333) { + .horizontal(@startColor: @gray, @endColor: @grayDark) { background-color: @endColor; background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ @@ -350,7 +350,7 @@ background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; } - .vertical(@startColor: #555, @endColor: #333) { + .vertical(@startColor: @gray, @endColor: @grayDark) { background-color: mix(@startColor, @endColor, 60%); background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ @@ -359,7 +359,7 @@ background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 background-repeat: repeat-x; } - .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + .directional(@startColor: @gray, @endColor: @grayDark, @deg: 45deg) { background-color: @endColor; background-repeat: repeat-x; background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ @@ -367,7 +367,7 @@ background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 } - .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + .horizontal-three-colors(@startColor: @lightBlueIcon, @midColor: @purple-l1, @colorStop: 50%, @endColor: @pink) { background-color: mix(@midColor, @endColor, 80%); background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); @@ -377,7 +377,7 @@ background-repeat: no-repeat; } - .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + .vertical-three-colors(@startColor: @lightBlueIcon, @midColor: @deepPurpleIcon, @colorStop: 50%, @endColor: @pink) { background-color: mix(@midColor, @endColor, 80%); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); @@ -386,7 +386,7 @@ background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); background-repeat: no-repeat; } - .radial(@innerColor: #555, @outerColor: #333) { + .radial(@innerColor: @gray, @outerColor: @grayDark) { background-color: @outerColor; background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); @@ -394,7 +394,7 @@ background-image: -o-radial-gradient(circle, @innerColor, @outerColor); background-repeat: no-repeat; } - .striped(@color: #555, @angle: 45deg) { + .striped(@color: @gray, @angle: 45deg) { background-color: @color; background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); @@ -404,7 +404,7 @@ } } -.checkeredBackground(@backgroundColor: #eee, @fillColor: #000, @fillOpacity: 0.25) { +.checkeredBackground(@backgroundColor: @gray-9, @fillColor: @black, @fillOpacity: 0.25) { background-image: url('data:image/svg+xml;charset=utf-8,\ \ \ @@ -435,14 +435,14 @@ // Button backgrounds // ------------------ -.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: #fff, @textColorHover: @textColor) { - +.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: @white, @textColorHover: @textColor) { + color: @textColor; border-color: @startColor @startColor darken(@startColor, 15%); border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); - + background-color: @startColor; - + .caret { border-top-color: @textColor; border-bottom-color: @textColor; @@ -453,7 +453,7 @@ color: @textColorHover; background-color: @hoverColor; } - + &.disabled, &[disabled] { color: @white; background-color: @sand-1; diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index fa23e08983..925f845c4c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -85,7 +85,7 @@ bottom: 0px; position: absolute; padding: 0px; - background: #fff; + background: @white; } .umb-dialog .umb-btn-toolbar .umb-control-group{ diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/document-type-editor.less b/src/Umbraco.Web.UI.Client/src/less/pages/document-type-editor.less deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less index 868a358d21..7c493ab18e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less @@ -1,5 +1,6 @@ @import "../fonts.less"; // Loading fonts @import "../variables.less"; // Loading variables +@import "../colors.less"; // Loading colors that were in variables abbr, address, diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 823daedf22..5a71635c4d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -1,13 +1,13 @@ // // Container styles // -------------------------------------------------- -.umb-property-editor { +.umb-property-editor { width: 100%; } .umb-property-editor-tiny { width: 60px; - + &.umb-editor-push { width:30%; min-width:0; @@ -30,7 +30,7 @@ } .umb-codeeditor{ - width: 99%; + width: 99%; } // displays property inline with preceeding @@ -139,7 +139,7 @@ margin-bottom: 0; vertical-align: middle; padding: 6px 10px; - background: #f7f7f7; + background: @gray-11; flex: 0 0 auto; } @@ -169,8 +169,8 @@ border: 1px solid @white; padding: 6px 10px; font-family: monospace; - border: 1px solid #dfdfe1; - background: #f7f7f7; + border: 1px solid @gray-8; + background: @gray-11; margin: 0 15px 0 3px; border-radius: 3px; } @@ -232,6 +232,7 @@ } .umb-mediapicker-multi > div { width:100%; + .umb-property-editor--limit-width(); } @@ -253,7 +254,7 @@ color: @ui-action-discreet-type-hover; border-color: @ui-action-discreet-type-hover; } - + &:focus { outline: none; .tabbing-active &:after { @@ -358,11 +359,11 @@ max-width: 100%; } .umb-mediapicker { - + .umb-sortable-thumbnails li { border:none; } - + } .umb-mediapicker .umb-sortable-thumbnails li { @@ -623,7 +624,7 @@ .imagecropper .umb-cropper__container { position: relative; margin-bottom: 10px; - max-width: 100%; + max-width: 100%; border: 1px solid @gray-10; @media (min-width: 769px) { @@ -822,7 +823,7 @@ background: @blueExtraDark; position: relative; user-select: all; - + .icon-trash { position: relative; cursor: pointer; @@ -843,7 +844,7 @@ border: none; background: @white; } - + .twitter-typeahead { margin: 10px; margin-top: 16px; @@ -880,6 +881,21 @@ .umb-datepicker p {margin-top:10px;} .umb-datepicker p a{color: @gray-3;} +// +// Link picker +// -------------------------------------------------- +.umb-linkpicker { + .umb-linkpicker__url { + width: 50%; + padding-right: 5px; + } + + .umb-linkpicker__anchor { + width: 50%; + padding-left: 5px; + } +} + // // Code mirror - even though this isn't a proprety editor right now, it could be so I'm putting the styles here // -------------------------------------------------- diff --git a/src/Umbraco.Web.UI.Client/src/less/rte-content.less b/src/Umbraco.Web.UI.Client/src/less/rte-content.less index 5fd7bbf1c3..3fe4e52f92 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte-content.less @@ -1,4 +1,5 @@ @import "variables.less"; +@import "colors.less"; .mce-content-body .umb-macro-holder { border: 3px dotted @pinkLight; diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index 445ed7eb4a..d6d38f540a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -3,7 +3,7 @@ .umb-rte { position: relative; - + .umb-property-editor--limit-width(); } @@ -116,7 +116,7 @@ } } -.umb-rte .mce-btn.mce-active, .umb-rte .mce-btn.mce-active:active, +.umb-rte .mce-btn.mce-active, .umb-rte .mce-btn.mce-active:active, .umb-rte .mce-btn.mce-active:hover, .umb-rte .mce-btn.mce-active:focus { background: @gray-9; border-color: transparent; @@ -158,7 +158,7 @@ } .umb-grid .umb-rte { - border: 1px solid #d8d7d9; + border: 1px solid @gray-8; max-width: none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index e8f6d4ee58..6071c4a5ef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -76,11 +76,11 @@ @gray-10: #F3F3F5; @gray-11: #F6F6F7; -@sand-1: hsl(22, 18%, 84%);// added 2019 -@sand-2: hsl(22, 34%, 88%);// added 2019 -@sand-5: hsl(22, 31%, 93%);// added 2019 -@sand-6: hsl(22, 29%, 95%);// added 2019 -@sand-7: hsl(22, 26%, 97%);// added 2019 +@sand-1: #DED4CF;// added 2019 +@sand-2: #EBDED6;// added 2019 +@sand-5: #F3ECE8;// added 2019 +@sand-6: #F6F1EF;// added 2019 +@sand-7: #F9F7F5;// added 2019 // Additional Icon Colours @@ -124,7 +124,7 @@ //@u-greyLight: #f2ebe6;// added 2019 @u-white: #f9f7f4;// added 2019 -@u-black: black;// added 2019 +@u-black: @black;// added 2019 // UI colors @@ -132,7 +132,7 @@ @ui-option-type: @blueExtraDark; @ui-option-type-hover: @blueMid; -@ui-option: white; +@ui-option: @white; @ui-option-hover: @sand-7; @ui-option-disabled-type: @gray-6; @@ -161,14 +161,14 @@ @ui-light-active-type-hover: @blueMid; -@ui-action: white; +@ui-action: @white; @ui-action-hover: @sand-7; @ui-action-type: @blueExtraDark; @ui-action-type-hover: @blueMid; @ui-action-border: @blueExtraDark; @ui-action-border-hover: @blueMid; -@ui-action-discreet: white; +@ui-action-discreet: @white; @ui-action-discreet-hover: @sand-7; @ui-action-discreet-type: @blueExtraDark; @ui-action-discreet-type-hover: @blueMid; @@ -194,96 +194,11 @@ - -.red{color: @red;} -.blue{color: @blue;} -.black{color: @black;} -.turquoise{color: @turquoise;} -.turquoise-d1{color: @turquoise-d1;} - -.text-warning { - color: @orange; -} -.text-error { - color: @red; -} -.text-success { - color: @green; -} - - -//icon colors for tree icons -.color-red, .color-red i{color: @red-d1 !important;} -.color-blue, .color-blue i{color: @turquoise-d1 !important;} -.color-orange, .color-orange i{color: @orange !important;} -.color-green, .color-green i{color: @green-d1 !important;} -.color-yellow, .color-yellow i{color: @yellowIcon !important;} - -/* Colors based on https://zavoloklom.github.io/material-design-color-palette/colors.html */ -.btn-color-black {background-color: @black;} -.color-black i { color: @black;} - -.btn-color-blue-grey {background-color: @blueGrey;} -.color-blue-grey, .color-blue-grey i { color: @blueGrey !important;} - -.btn-color-grey{background-color: @grayIcon;} -.color-grey, .color-grey i { color: @grayIcon !important; } - -.btn-color-brown{background-color: @brownIcon;} -.color-brown, .color-brown i { color: @brownIcon !important; } - -.btn-color-blue{background-color: @blueIcon;} -.color-blue, .color-blue i { color: @blueIcon !important; } - -.btn-color-light-blue{background-color: @lightBlueIcon;} -.color-light-blue, .color-light-blue i {color: @lightBlueIcon !important;} - -.btn-color-cyan{background-color: @cyanIcon;} -.color-cyan, .color-cyan i { color: @cyanIcon !important; } - -.btn-color-green{background-color: @greenIcon;} -.color-green, .color-green i { color: @greenIcon !important; } - -.btn-color-light-green{background-color: @lightGreenIcon;} -.color-light-green, .color-light-green i {color: @lightGreenIcon !important; } - -.btn-color-lime{background-color: @limeIcon;} -.color-lime, .color-lime i { color: @limeIcon !important; } - -.btn-color-yellow{background-color: @yellowIcon;} -.color-yellow, .color-yellow i { color: @yellowIcon !important; } - -.btn-color-amber{background-color: @amberIcon;} -.color-amber, .color-amber i { color: @amberIcon !important; } - -.btn-color-orange{background-color: @orangeIcon;} -.color-orange, .color-orange i { color: @orangeIcon !important; } - -.btn-color-deep-orange{background-color: @deepOrangeIcon;} -.color-deep-orange, .color-deep-orange i { color: @deepOrangeIcon !important; } - -.btn-color-red{background-color: @redIcon;} -.color-red, .color-red i { color: @redIcon !important; } - -.btn-color-pink{background-color: @pinkIcon;} -.color-pink, .color-pink i { color: @pinkIcon !important; } - -.btn-color-purple{background-color: @purpleIcon;} -.color-purple, .color-purple i { color: @purpleIcon !important; } - -.btn-color-deep-purple{background-color: @deepPurpleIcon;} -.color-deep-purple, .color-deep-purple i { color: @deepPurpleIcon !important; } - -.btn-color-indigo{background-color: @indigoIcon;} -.color-indigo, .color-indigo i { color: @indigoIcon !important; } - - - // Scaffolding // ------------------------- @appHeaderHeight: 55px; @bodyBackground: @gray-10; -@textColor: #000; +@textColor: @black; @editorHeaderHeight: 70px; @editorFooterHeight: 50px; diff --git a/src/Umbraco.Web.UI.Client/src/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/navigation.controller.js index b585d22e9f..194c45afe6 100644 --- a/src/Umbraco.Web.UI.Client/src/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/navigation.controller.js @@ -510,6 +510,14 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar if (!event) { return; } + closeTree(); + }; + + $scope.onOutsideClick = function() { + closeTree(); + }; + + function closeTree() { if (!appState.getGlobalState("touchDevice")) { treeActive = false; $timeout(function () { @@ -518,7 +526,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } }, 300); } - }; + } $scope.toggleLanguageSelector = function () { $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index c4cb821818..dc40338d01 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -7,6 +7,31 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi .controller("previewController", function ($scope, $window, $location) { + $scope.currentCulture = {iso:'', title:'...', icon:'icon-loading'} + var cultures = []; + + $scope.tabbingActive = false; + // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. + // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 + function handleFirstTab(evt) { + if (evt.keyCode === 9) { + $scope.tabbingActive = true; + $scope.$digest(); + window.removeEventListener('keydown', handleFirstTab); + window.addEventListener('mousedown', disableTabbingActive); + } + } + + function disableTabbingActive(evt) { + $scope.tabbingActive = false; + $scope.$digest(); + window.removeEventListener('mousedown', disableTabbingActive); + window.addEventListener("keydown", handleFirstTab); + } + + window.addEventListener("keydown", handleFirstTab); + + //gets a real query string value function getParameterByName(name, url) { if (!url) url = $window.location.href; @@ -75,15 +100,55 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.valueAreLoaded = false; $scope.devices = [ - { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, - { name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" }, - { name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" }, - { name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" }, - { name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" }, - { name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" } + { name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Browser" }, + { name: "desktop", css: "desktop shadow", icon: "icon-display", title: "Desktop" }, + { name: "laptop - 1366px", css: "laptop shadow", icon: "icon-laptop", title: "Laptop" }, + { name: "iPad portrait - 768px", css: "iPad-portrait shadow", icon: "icon-ipad", title: "Tablet portrait" }, + { name: "iPad landscape - 1024px", css: "iPad-landscape shadow", icon: "icon-ipad flip", title: "Tablet landscape" }, + { name: "smartphone portrait - 480px", css: "smartphone-portrait shadow", icon: "icon-iphone", title: "Smartphone portrait" }, + { name: "smartphone landscape - 320px", css: "smartphone-landscape shadow", icon: "icon-iphone flip", title: "Smartphone landscape" } ]; $scope.previewDevice = $scope.devices[0]; + $scope.sizeOpen = false; + $scope.cultureOpen = false; + + $scope.toggleSizeOpen = function() { + $scope.sizeOpen = toggleMenu($scope.sizeOpen); + } + $scope.toggleCultureOpen = function() { + $scope.cultureOpen = toggleMenu($scope.cultureOpen); + } + + function toggleMenu(isCurrentlyOpen) { + if (isCurrentlyOpen === false) { + closeOthers(); + return true; + } else { + return false; + } + } + function closeOthers() { + $scope.sizeOpen = false; + $scope.cultureOpen = false; + } + + $scope.windowClickHandler = function() { + closeOthers(); + } + function windowBlurHandler() { + closeOthers(); + $scope.$digest(); + } + + var win = angular.element($window); + + win.on("blur", windowBlurHandler); + + $scope.$on("$destroy", function () { + win.off("blur", handleBlwindowBlurHandlerur ); + }); + function setPageUrl(){ $scope.pageId = $location.search().id || getParameterByName("id"); @@ -123,6 +188,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.onFrameLoaded = function (iframe) { $scope.frameLoaded = true; configureSignalR(iframe); + + $scope.currentCultureIso = $location.search().culture || null; }; /*****************************************************************************/ @@ -136,17 +203,32 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi /*****************************************************************************/ /* Change culture */ /*****************************************************************************/ - $scope.changeCulture = function (culture) { - if($location.search().culture !== culture){ + $scope.changeCulture = function (iso) { + if($location.search().culture !== iso) { $scope.frameLoaded = false; - $location.search("culture", culture); + $scope.currentCultureIso = iso; + $location.search("culture", iso); setPageUrl(); } }; - - $scope.isCurrentCulture = function(culture) { - return $location.search().culture === culture; + $scope.registerCulture = function(iso, title, isDefault) { + var cultureObject = {iso: iso, title: title, isDefault: isDefault}; + cultures.push(cultureObject); } + + $scope.$watch("currentCultureIso", function(oldIso, newIso) { + // if no culture is selected, we will pick the default one: + if ($scope.currentCultureIso === null) { + $scope.currentCulture = cultures.find(function(culture) { + return culture.isDefault === true; + }) + return; + } + $scope.currentCulture = cultures.find(function(culture) { + return culture.iso === $scope.currentCultureIso; + }) + }); + }) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js index 67604aca44..3b405333bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.controller.js @@ -1,98 +1,99 @@ - (function() { - "use strict"; +(function () { + "use strict"; - function CopyController($scope, localizationService, eventsService, entityHelper) { + function CopyController($scope, localizationService, eventsService, entityHelper) { - var vm = this; + var vm = this; - vm.labels = {}; - vm.hideSearch = hideSearch; - vm.selectResult = selectResult; - vm.onSearchResults = onSearchResults; - vm.submit = submit; - vm.close = close; + vm.labels = {}; + vm.hideSearch = hideSearch; + vm.selectResult = selectResult; + vm.onSearchResults = onSearchResults; + vm.onToggle = toggleHandler; + vm.submit = submit; + vm.close = close; - var dialogOptions = $scope.model; - var node = dialogOptions.currentNode; + var dialogOptions = $scope.model; + var node = dialogOptions.currentNode; - $scope.model.relateToOriginal = true; - $scope.dialogTreeApi = {}; + $scope.model.relateToOriginal = true; + $scope.dialogTreeApi = {}; - vm.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; + vm.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + }; - // get entity type based on the section - $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section); + // get entity type based on the section + $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section); - function onInit() { + function onInit() { - var labelKeys = [ - "general_copy", - "defaultdialogs_relateToOriginalLabel" - ]; + var labelKeys = [ + "general_copy", + "defaultdialogs_relateToOriginalLabel" + ]; - localizationService.localizeMany(labelKeys).then(function (data) { + localizationService.localizeMany(labelKeys).then(function (data) { - vm.labels.title = data[0]; - vm.labels.relateToOriginal = data[1]; + vm.labels.title = data[0]; + vm.labels.relateToOriginal = data[1]; - setTitle(vm.labels.title); - }); - } + setTitle(vm.labels.title); + }); + } - function setTitle(value) { - if (!$scope.model.title) { - $scope.model.title = value; - } - } + function setTitle(value) { + if (!$scope.model.title) { + $scope.model.title = value; + } + } - function nodeSelectHandler(args) { - if (args && args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } + function nodeSelectHandler(args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } - //eventsService.emit("editors.content.copyController.select", args); + //eventsService.emit("editors.content.copyController.select", args); - if ($scope.model.target) { - //un-select if there's a current one selected - $scope.model.target.selected = false; - } + if ($scope.model.target) { + //un-select if there's a current one selected + $scope.model.target.selected = false; + } - $scope.model.target = args.node; - $scope.model.target.selected = true; - } + $scope.model.target = args.node; + $scope.model.target.selected = true; + } - function nodeExpandedHandler(args) { - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - } + function nodeExpandedHandler(args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } - function hideSearch() { - vm.searchInfo.showSearch = false; - vm.searchInfo.searchFromId = null; - vm.searchInfo.searchFromName = null; - vm.searchInfo.results = []; - } + function hideSearch() { + vm.searchInfo.showSearch = false; + vm.searchInfo.searchFromId = null; + vm.searchInfo.searchFromName = null; + vm.searchInfo.results = []; + } - // method to select a search result - function selectResult(evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler({ event: evt, node: result }); - } + // method to select a search result + function selectResult(evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler({ event: evt, node: result }); + } - //callback when there are search results - function onSearchResults(results) { - vm.searchInfo.results = results; - vm.searchInfo.showSearch = true; - } + //callback when there are search results + function onSearchResults(results) { + vm.searchInfo.results = results; + vm.searchInfo.showSearch = true; + } $scope.onTreeInit = function () { $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); @@ -100,19 +101,19 @@ } - // Mini list view - $scope.selectListViewNode = function (node) { - node.selected = node.selected === true ? false : true; - nodeSelectHandler({ node: node }); - }; + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({ node: node }); + }; - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; - function openMiniListView(node) { - $scope.miniListView = node; - } + function openMiniListView(node) { + $scope.miniListView = node; + } function submit() { if ($scope.model && $scope.model.submit) { @@ -126,9 +127,16 @@ } } - onInit(); - } + function toggleHandler(type) { + // If the relateToOriginal toggle is clicked + if (type === "relate") { + $scope.model.relateToOriginal = !$scope.model.relateToOriginal; + } + } - angular.module("umbraco").controller("Umbraco.Editors.CopyController", CopyController); + onInit(); + } + + angular.module("umbraco").controller("Umbraco.Editors.CopyController", CopyController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html index 07c44e90ee..86c0186374 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/copy/copy.html @@ -2,7 +2,7 @@ -
+
-
-
- -
-
+ + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index 6ba2ec0270..a7d2dbbee2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -1,4 +1,4 @@ -
+
@@ -16,7 +16,7 @@
- + - + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html index 9cd245b0d5..eb12fc7940 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content.html @@ -27,6 +27,12 @@ + + +
+ This item is in the Recycle Bin +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html index 57a4fdae05..82b21e4c3b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/forms/umb-checkbox.html @@ -16,5 +16,7 @@ - {{vm.text}} + + + {{vm.text}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html new file mode 100644 index 0000000000..62b2052771 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/member/umb-member-node-info.html @@ -0,0 +1,50 @@ +
+
+ + + +
+
{{ group.label }}
+
+
+ + + +
+
+ +
+ +
+ + + + + + {{node.createDateFormatted}} by {{ node.owner.name }} + + + + {{node.updateDateFormatted}} + + + + + + + + +
{{ node.id }}
+ {{ node.key }} +
+ +
+
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 46660fc685..c2f9ceebc4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -6,12 +6,12 @@
-
+
{{inheritsFrom}} -
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html index 6ef4378106..ee9034fac6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html @@ -8,10 +8,10 @@
- + - - - diff --git a/src/Umbraco.Web.UI.Client/src/views/content/assigndomain.html b/src/Umbraco.Web.UI.Client/src/views/content/assigndomain.html index ecb1e34e0c..42876cc27a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/assigndomain.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/assigndomain.html @@ -45,7 +45,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/delete.html b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/delete.html index 324fb2ae9f..f7f036178f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html deleted file mode 100644 index c29b52d96e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/members/membersdashboardintro.html +++ /dev/null @@ -1,10 +0,0 @@ -

Start here

- -

Get started with Members right now

-

Use the tool below to search for an existing member.

- -

More about members

- -
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html index 6bff0bba9b..95c6af9f79 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/profiler.html @@ -27,7 +27,7 @@
- + 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 474b07d12c..66983bbc05 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 @@ -10,7 +10,11 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic var evts = []; var vm = this; - + + vm.header = {}; + vm.header.editorfor = "visuallyHiddenTexts_newDataType"; + vm.header.setPageTitle = true; + //setup scope vars vm.page = {}; vm.page.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html index 0bb2b01e31..2d481de852 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/edit.html @@ -8,7 +8,9 @@ hide-icon="true" hide-description="true" hide-alias="true" - navigation="vm.page.navigation"> + navigation="vm.page.navigation" + editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html index c84eeeedae..e7fdf4f96e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/delete.html @@ -11,7 +11,7 @@
+ on-cancel="cancel" confirm-button-style="danger">
@@ -31,7 +31,7 @@ - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 9d73aa8838..b04cccbc0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -64,7 +64,8 @@ if (infiniteMode) { documentTypeId = $scope.model.id; create = $scope.model.create; - noTemplate = $scope.model.notemplate; + if (create && !documentTypeId) documentTypeId = -1; + noTemplate = $scope.model.notemplate || $scope.model.noTemplate; isElement = $scope.model.isElement; allowVaryByCulture = $scope.model.allowVaryByCulture; vm.submitButtonKey = "buttons_saveAndClose"; diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html index 5fd453bcd7..8f84a0cceb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html @@ -9,7 +9,7 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/delete.html b/src/Umbraco.Web.UI.Client/src/views/macros/delete.html index e6c24507b6..834d7f97ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/delete.html @@ -5,6 +5,6 @@ Are you sure you want to delete {{vm.name}}?

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/edit.html b/src/Umbraco.Web.UI.Client/src/views/macros/edit.html index ddb5f98781..9640419ee2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/edit.html @@ -6,7 +6,8 @@ alias="vm.macro.alias" hide-description="true" hide-icon="true" - navigation="vm.page.navigation"> + navigation="vm.page.navigation"editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js index 272e5dd03a..3261739d36 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/macros/macros.edit.controller.js @@ -11,7 +11,10 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta var vm = this; vm.promises = {}; - + vm.header = {}; + vm.header.editorfor = "general_macro"; + vm.header.setPageTitle = true; + vm.page = {}; vm.page.loading = false; vm.page.saveButtonState = "init"; diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/views/parameters.html b/src/Umbraco.Web.UI.Client/src/views/macros/views/parameters.html index 6ea79fe16d..ccdb41384f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/views/parameters.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/views/parameters.html @@ -10,13 +10,13 @@
- +
{{parameter.label}} ({{parameter.key}}){{parameter.editor}}
- Edit - Remove + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html index 971d8f30ee..c059710ebb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/delete.html @@ -27,7 +27,7 @@ - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js new file mode 100644 index 0000000000..635c536816 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.controller.js @@ -0,0 +1,11 @@ +(function () { + "use strict"; + + function MemberAppContentController($scope) { + + var vm = this; + + } + + angular.module("umbraco").controller("Umbraco.Editors.Member.Apps.ContentController", MemberAppContentController); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html new file mode 100644 index 0000000000..29df5ba638 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/content/content.html @@ -0,0 +1,17 @@ +
+ +
+ +
+
{{ group.label }}
+
+ +
+ + + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html b/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html new file mode 100644 index 0000000000..bd9cb98b64 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/member/apps/info/info.html @@ -0,0 +1,4 @@ + + diff --git a/src/Umbraco.Web.UI.Client/src/views/member/edit.html b/src/Umbraco.Web.UI.Client/src/views/member/edit.html index 58bebe1e34..3fec222350 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/member/edit.html @@ -14,29 +14,22 @@ hide-icon="true" hide-description="true" hide-alias="true" + navigation="content.apps" + on-select-navigation-item="appChanged(item)" show-back-button="showBack()" on-back="onBack()" editorfor="header.editorfor" setpagetitle="header.setPageTitle"> - - -
- -
-
{{ group.label }}
+ + +
+
+
- -
- - - -
-
- -
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 263f78f314..c0967df232 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -45,9 +45,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); $scope.page.loading = false; @@ -59,9 +57,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR .then(function (data) { $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); $scope.page.loading = false; @@ -90,9 +86,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR $scope.content = data; - setHeaderNameState($scope.content); - - editorState.set($scope.content); + init(); if (!infiniteMode) { var path = buildTreePath(data); @@ -121,8 +115,48 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR } - function setHeaderNameState(content) { - $scope.page.nameLocked = true; + function init() { + + var content = $scope.content; + + // we need to check wether an app is present in the current data, if not we will present the default app. + var isAppPresent = false; + + // on first init, we dont have any apps. but if we are re-initializing, we do, but ... + if ($scope.app) { + + // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) + _.forEach(content.apps, function (app) { + if (app === $scope.app) { + isAppPresent = true; + } + }); + + // if we did reload our DocType, but still have the same app we will try to find it by the alias. + if (isAppPresent === false) { + _.forEach(content.apps, function (app) { + if (app.alias === $scope.app.alias) { + isAppPresent = true; + app.active = true; + $scope.appChanged(app); + } + }); + } + + } + + // if we still dont have a app, lets show the first one: + if (isAppPresent === false) { + content.apps[0].active = true; + $scope.appChanged(content.apps[0]); + } + + if (content.membershipScenario === 0) { + $scope.page.nameLocked = true; + } + + editorState.set($scope.content); + } /** Just shows a simple notification that there are client side validation issues to be fixed */ @@ -184,6 +218,15 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR }; + $scope.appChanged = function (app) { + $scope.app = app; + + // setup infinite mode + if (infiniteMode) { + $scope.page.submitButtonLabelKey = "buttons_saveAndClose"; + } + } + $scope.showBack = function () { return !!listName; } diff --git a/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html b/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html index fff0348b87..17a71fa9de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/membergroups/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html index 84eeaa0182..d87b6737f4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/delete.html @@ -16,7 +16,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html index 9568536d5f..14aeaf1b58 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/repo.html @@ -7,19 +7,31 @@
- -
{{category.name}}
-
+ {{category.name}} +
@@ -30,7 +42,7 @@ @@ -64,7 +78,7 @@ @@ -140,9 +156,9 @@
@@ -274,7 +290,7 @@
@@ -304,7 +320,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html index 0adbc2aaa2..b5d187eb57 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html index fa1baa1eff..4964007fb0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/delete.html @@ -12,7 +12,7 @@ Are you sure you want to delete {{currentNode.name}}?

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html index 1056b45f9d..af2dba1d7b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.prevalues.html @@ -21,7 +21,7 @@
- Remove +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html index 0aa4b5e561..742f5bafa4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.prevalues.html @@ -53,7 +53,7 @@
- Cancel +
@@ -65,8 +65,8 @@

{{item.alias}} ({{item.width}}px × {{item.height}}px)

- Edit - Remove + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html index ba08aa2293..c6675ccec8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/includeproperties.prevalues.html @@ -12,7 +12,7 @@
- +
@@ -31,7 +31,7 @@
- (system field) + (system field)
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 80e2fa7ce7..c9d4caf312 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 @@ -99,6 +99,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl function sync() { $scope.model.value = $scope.ids.join(); + removeAllEntriesAction.isDisabled = $scope.ids.length === 0; }; function setDirty() { @@ -247,6 +248,31 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl return true; } + function removeAllEntries() { + $scope.mediaItems.length = 0;// AngularJS way to empty the array. + $scope.ids.length = 0;// AngularJS way to empty the array. + sync(); + setDirty(); + } + + var removeAllEntriesAction = { + labelKey: 'clipboard_labelForRemoveAllEntries', + labelTokens: [], + icon: 'trash', + method: removeAllEntries, + isDisabled: true + }; + + if (multiPicker === true) { + var propertyActions = [ + removeAllEntriesAction + ]; + + if ($scope.umbProperty) { + $scope.umbProperty.setPropertyActions(propertyActions); + } + } + $scope.sortableOptions = { containment: 'parent', cursor: 'move', diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 635a80dbe9..d625917afb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -55,8 +55,8 @@ }); function setCurrentNode(node) { - vm.currentNode = node; updateModel(); + vm.currentNode = node; } var copyAllEntries = function() { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js index 4a9a07428d..6e807ffaa4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.controller.js @@ -53,10 +53,6 @@ }); } - $scope.canAdd = function () { - return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length; - } - $scope.remove = function (index) { $scope.model.value.splice(index, 1); } @@ -112,6 +108,7 @@ }); }); } + $scope.canAdd = function () { return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) { return !_.find($scope.model.value, function (c) { @@ -120,7 +117,6 @@ }); } - $scope.openElemTypeModal = function ($event, config) { //we have to add the alias to the objects (they are stored as ncAlias) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index b147e5ccca..d0b5823f74 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -3,6 +3,6 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index 72b6fb7279..ee2929e3ea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -2,16 +2,12 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js index 884cc62d43..4a7fff99f8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js @@ -15,7 +15,22 @@ function textAreaController($scope, validationMessageService) { if ($scope.model.config && $scope.model.config.maxChars) { $scope.model.maxlength = true; } - + + $scope.$on("formSubmitting", function() { + if ($scope.isLengthValid()) { + $scope.textareaFieldForm.textarea.$setValidity("maxChars", true); + } else { + $scope.textareaFieldForm.textarea.$setValidity("maxChars", false); + } + }); + + $scope.isLengthValid = function() { + if (!$scope.model.maxlength) { + return true; + } + return $scope.model.config.maxChars >= $scope.model.count; + } + $scope.model.change = function () { if ($scope.model.value) { $scope.model.count = $scope.model.value.length; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html index 04bd8590d2..d255c4a5d6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.html @@ -10,7 +10,7 @@
%0% characters left.
-
+
Maximum %0% characters, %1% too many.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index e86d8caef4..b47c3584b3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -11,7 +11,19 @@ function textboxController($scope, validationMessageService) { // if no max is specified in the config $scope.model.config.maxChars = 500; } - + + $scope.$on("formSubmitting", function() { + if ($scope.isLengthValid()) { + $scope.textboxFieldForm.textbox.$setValidity("maxChars", true); + } else { + $scope.textboxFieldForm.textbox.$setValidity("maxChars", false); + } + }); + + $scope.isLengthValid = function() { + return $scope.model.config.maxChars >= $scope.model.count; + } + $scope.model.change = function () { if ($scope.model.value) { $scope.model.count = $scope.model.value.length; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index e1f5dac733..5d86259e93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -19,7 +19,7 @@

{{model.label}} %0% characters left.

%0% characters left.

-
+

{{model.label}} Maximum %0% characters, %1% too many.

diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html index b937d0869d..246e5bdb7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js index 44fbf6ffe9..74b3e31b87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.controller.js @@ -10,6 +10,10 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource, var vm = this; + vm.header = {}; + vm.header.editorfor = "relationType_tabRelationType"; + vm.header.setPageTitle = true; + vm.page = {}; vm.page.loading = false; vm.page.saveButtonState = "init"; diff --git a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html index 2c86161bda..35e7aa5176 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html @@ -8,7 +8,9 @@ alias="vm.relationType.alias" hide-description="true" hide-icon="true" - navigation="vm.page.navigation"> + navigation="vm.page.navigation" + editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html b/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html index aae686d5ce..c8f6a2a9b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/scripts/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html b/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html index d32a7197e1..abd082d413 100644 --- a/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/stylesheets/delete.html @@ -5,7 +5,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/delete.html b/src/Umbraco.Web.UI.Client/src/views/templates/delete.html index 7c3af5937e..d45353db26 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/delete.html @@ -12,7 +12,7 @@ Are you sure you want to delete {{currentNode.name}}?

- + diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index ee77e4c14e..b35a29d2de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -207,7 +207,7 @@ @@ -249,7 +249,7 @@
0 && filterCollection && filterCollection.length > 0) { + for (var i = 0; i < selectedCollection.length; i++) { + for (var j = 0; j < filterCollection.length; j++) { + if (filterCollection[j][keyField] === selectedCollection[i]) { + filterCollection[j].selected = true; + } + } + } + } + } + function getSortLabel(sortKey, sortDirection) { var found = _.find(vm.userSortData, function (i) { @@ -467,6 +511,7 @@ vm.usersOptions.userStates.splice(index, 1); } + updateLocation("userStates", vm.usersOptions.userStates.join(",")); getUsers(); } @@ -483,20 +528,28 @@ vm.usersOptions.userGroups.splice(index, 1); } + updateLocation("userGroups", vm.usersOptions.userGroups.join(",")); getUsers(); } function setOrderByFilter(value, direction) { vm.usersOptions.orderBy = value; vm.usersOptions.orderDirection = direction; + updateLocation("orderBy", value); + updateLocation("orderDirection", direction); getUsers(); } function changePageNumber(pageNumber) { vm.usersOptions.pageNumber = pageNumber; + updateLocation("pageNumber", pageNumber); getUsers(); } + function updateLocation(key, value) { + $location.search(key, value); + } + function createUser(addUserForm) { if (formHelper.submitForm({ formCtrl: addUserForm, scope: $scope })) { @@ -575,17 +628,53 @@ } function goToUser(user) { - $location.path(pathToUser(user)).search("create", null).search("invite", null); + $location.path(pathToUser(user)) + .search("orderBy", vm.usersOptions.orderBy) + .search("orderDirection", vm.usersOptions.orderDirection) + .search("pageNumber", vm.usersOptions.pageNumber) + .search("userStates", getUsersOptionsFilterCollectionAsDelimitedStringOrNull(vm.usersOptions.userStates)) + .search("userGroups", getUsersOptionsFilterCollectionAsDelimitedStringOrNull(vm.usersOptions.userGroups)) + .search("create", null) + .search("invite", null); + } + + function getUsersOptionsFilterCollectionAsDelimitedStringOrNull(collection) { + if (collection && collection.length > 0) { + return collection.join(","); + } + + return null; } function getEditPath(user) { - return pathToUser(user) + "?mculture=" + $location.search().mculture; + return pathToUser(user) + usersOptionsAsQueryString(); } - + function pathToUser(user) { return "/users/users/user/" + user.id; } + function usersOptionsAsQueryString() { + var qs = "?orderBy=" + vm.usersOptions.orderBy + + "&orderDirection=" + vm.usersOptions.orderDirection + + "&pageNumber=" + vm.usersOptions.pageNumber; + + qs += addUsersOptionsFilterCollectionToQueryString("userStates", vm.usersOptions.userStates); + qs += addUsersOptionsFilterCollectionToQueryString("userGroups", vm.usersOptions.userGroups); + + qs += "&mculture=" + $location.search().mculture; + + return qs; + } + + function addUsersOptionsFilterCollectionToQueryString(name, collection) { + if (collection && collection.length > 0) { + return "&" + name + "=" + collection.join(","); + } + + return ""; + } + // helpers function getUsers() { @@ -604,6 +693,7 @@ formatDates(vm.users); setUserDisplayState(vm.users); vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates); + initUserStateSelections(); vm.loading = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index 1ed148a23f..afaaf865c8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -505,6 +505,7 @@

diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 803029b081..d1de4de358 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -351,6 +351,10 @@ 9000 / http://localhost:9000/ + http://localhost:8600/ + 8500 + / + http://localhost:8500 False False diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index ee34071328..41ab319164 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -1,4 +1,4 @@ -@using Umbraco.Core +@using Umbraco.Core @using ClientDependency.Core @using ClientDependency.Core.Mvc @using Umbraco.Core.Composing @@ -10,8 +10,7 @@ @{ var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); - Html - .RequiresCss("assets/css/canvasdesigner.css", "Umbraco"); + Html.RequiresCss("assets/css/canvasdesigner.css", "Umbraco"); } @@ -25,7 +24,7 @@ new BasicPath("Umbraco", Current.IOHelper.ResolveUrl(Current.Configs.Global().UmbracoPath))) - +
@if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false) @@ -37,31 +36,40 @@

-
-
- -
-
    -
  • - -
  • +
+ + +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 0deac8b50f..a74c6c6243 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -620,6 +620,7 @@ Fortryd Celle margen Vælg + Ryd Luk Luk vindue Kommentar diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 3f9564f8b2..7a3575f98f 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -301,6 +301,7 @@ Select the date and time to publish and/or unpublish the content item. Create new Paste from clipboard + This item is in the Recycle Bin Create a new Content Template from '%0%' @@ -625,6 +626,7 @@ Cancel Cell margin Choose + Clear Close Close Window Comment @@ -2178,6 +2180,7 @@ To manage your website, simply open the Umbraco back office and start adding con Partial View Partial View Macro Member + Data type References 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 e4b88400e4..db84d945b0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -305,6 +305,7 @@ Select the date and time to publish and/or unpublish the content item. Create new Paste from clipboard + This item is in the Recycle Bin Create a new Content Template from '%0%' @@ -631,6 +632,7 @@ Cancel Cell margin Choose + Clear Close Close Window Comment @@ -2194,6 +2196,7 @@ To manage your website, simply open the Umbraco back office and start adding con Partial View Partial View Macro Member + Data type References diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml index 8ddf2fae26..31846e9e07 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/sv.xml @@ -361,6 +361,7 @@ Avbryt Cellmarginal Välj + Rensa Stäng Stäng fönstret Kommentar diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml index 393157bcf8..4a915a444b 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Embed.cshtml @@ -1,7 +1,10 @@ @model dynamic @using Umbraco.Web.Templates - +@{ + string embedValue = Convert.ToString(Model.value); + embedValue = embedValue.DetectIsJson() ? Model.value.preview : Model.value; +}
- @Html.Raw(Model.value) + @Html.Raw(embedValue)
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index 00b556fd34..fa998a9856 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -1,113 +1,245 @@ - - - - - - - - - - - - - 1 - - - - - - - - your@email.here - - - - - Preview modeClick to end]]> - - - - throw - - - ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess - - - assets/img/login.jpg - - - - - - false - - true - - false - - - - - - - - - - - - + + + + + + + + + + + + + 1 + + + + + + + + your@email.here + + + + + + Preview mode + + … + + + Click to end + + + + ]]> + + + + throw + + + ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess + + + assets/img/login.jpg + + + + + + false + + true + + false + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index a7f6e1e0f1..9e60a9499c 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -38,7 +38,140 @@ - Preview modeClick to end]]> + + Preview mode + + … + + + Click to end + + + + ]]> + + + \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs index 0422d3c674..5c997efeaf 100644 --- a/src/Umbraco.Web/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentCacheRefresher.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; @@ -140,6 +141,7 @@ namespace Umbraco.Web.Cache public class JsonPayload { + [JsonConstructor] public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) { Id = id; diff --git a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs index dfb85aad6a..9d0c8c8450 100644 --- a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs @@ -26,7 +26,6 @@ namespace Umbraco.Web.Cache public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654"); private readonly IPublishedSnapshotService _publishedSnapshotService; - private readonly IDomainService _domainService; public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs index 8c251cacd2..add7e2f16a 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; + private ContentApp _memberApp; public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { @@ -45,6 +46,16 @@ namespace Umbraco.Web.ContentApps case IMedia _: return null; + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbContent", + Name = "Content", + Icon = Constants.Icons.Content, + View = "views/member/apps/content/content.html", + Weight = Weight + }); + default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } diff --git a/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs index 49be194349..fac03c43d0 100644 --- a/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentInfoContentAppFactory.cs @@ -13,6 +13,7 @@ namespace Umbraco.Web.ContentApps private ContentApp _contentApp; private ContentApp _mediaApp; + private ContentApp _memberApp; public ContentApp GetContentAppFor(object o, IEnumerable userGroups) { @@ -37,6 +38,15 @@ namespace Umbraco.Web.ContentApps View = "views/media/apps/info/info.html", Weight = Weight }); + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbInfo", + Name = "Info", + Icon = "icon-info", + View = "views/member/apps/info/info.html", + Weight = Weight + }); default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); diff --git a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs index e715dc569e..3e0dea0f5e 100644 --- a/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ListViewContentAppFactory.cs @@ -45,6 +45,8 @@ namespace Umbraco.Web.ContentApps entityType = "media"; dtdId = Core.Constants.DataTypes.DefaultMediaListView; break; + case IMember member: + return null; default: throw new NotSupportedException($"Object type {o.GetType()} is not supported here."); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d6fe5060a9..b4e5064737 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1692,6 +1692,7 @@ namespace Umbraco.Web.Editors var permission = Services.UserService.GetPermissions(Security.CurrentUser, node.Path); + if (permission.AssignedPermissions.Contains(ActionAssignDomain.ActionLetter.ToString(), StringComparer.Ordinal) == false) { var response = Request.CreateResponse(HttpStatusCode.BadRequest); diff --git a/src/Umbraco.Web/Editors/HelpController.cs b/src/Umbraco.Web/Editors/HelpController.cs index ccbbcaeee8..39dbbc435c 100644 --- a/src/Umbraco.Web/Editors/HelpController.cs +++ b/src/Umbraco.Web/Editors/HelpController.cs @@ -13,14 +13,23 @@ namespace Umbraco.Web.Editors { var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree); - if (_httpClient == null) - _httpClient = new HttpClient(); + try + { - //fetch dashboard json and parse to JObject - var json = await _httpClient.GetStringAsync(url); - var result = JsonConvert.DeserializeObject>(json); - if (result != null) - return result; + if (_httpClient == null) + _httpClient = new HttpClient(); + + //fetch dashboard json and parse to JObject + var json = await _httpClient.GetStringAsync(url); + var result = JsonConvert.DeserializeObject>(json); + if (result != null) + return result; + + } + catch (HttpRequestException rex) + { + Logger.Info(GetType(), $"Check your network connection, exception: {rex.Message}"); + } return new List(); } diff --git a/src/Umbraco.Web/Editors/MacrosController.cs b/src/Umbraco.Web/Editors/MacrosController.cs index 26138b7f26..cda9bacf73 100644 --- a/src/Umbraco.Web/Editors/MacrosController.cs +++ b/src/Umbraco.Web/Editors/MacrosController.cs @@ -326,7 +326,6 @@ namespace Umbraco.Web.Editors /// Finds partial view files in app plugin folders. /// /// - /// The . /// private IEnumerable FindPartialViewFilesInPluginFolders() { diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 302c1d8271..4f4d61b4a8 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -64,7 +64,8 @@ namespace Umbraco.Web var htmlBadge = String.Format(Current.Configs.Settings().Content.PreviewBadge, Current.IOHelper.ResolveUrl(Current.Configs.Global().UmbracoPath), - Current.UmbracoContext.HttpContext.Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Path)); + Current.UmbracoContext.HttpContext.Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Path), + Current.UmbracoContext.PublishedRequest.PublishedContent.Id); return new MvcHtmlString(htmlBadge); } return new MvcHtmlString(""); diff --git a/src/Umbraco.Web/Install/FilePermissionHelper.cs b/src/Umbraco.Web/Install/FilePermissionHelper.cs index 53b0f953b9..877c2488f6 100644 --- a/src/Umbraco.Web/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Web/Install/FilePermissionHelper.cs @@ -46,8 +46,8 @@ namespace Umbraco.Web.Install /// /// This will test the directories for write access /// - /// - /// + /// + /// /// /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs index f4200c4963..2f53f6f73b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberDisplay.cs @@ -1,4 +1,8 @@ -using System.Runtime.Serialization; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Models.ContentEditing { @@ -8,11 +12,30 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "content", Namespace = "")] public class MemberDisplay : ListViewAwareContentItemDisplayBase { + public MemberDisplay() + { + // MemberProviderFieldMapping = new Dictionary(); + ContentApps = new List(); + } + [DataMember(Name = "username")] public string Username { get; set; } [DataMember(Name = "email")] public string Email { get; set; } + //[DataMember(Name = "membershipScenario")] + //public MembershipScenario MembershipScenario { get; set; } + + // /// + // /// This is used to indicate how to map the membership provider properties to the save model, this mapping + // /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, + // /// or if we are editing a member that is not an Umbraco member (custom provider) + // /// + // [DataMember(Name = "fieldConfig")] + // public IDictionary MemberProviderFieldMapping { get; set; } + + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs index 67617807fc..d0a843ff2f 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Dictionary; using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; @@ -20,15 +21,17 @@ namespace Umbraco.Web.Models.Mapping private readonly ILogger _logger; private readonly IMediaService _mediaService; private readonly IMediaTypeService _mediaTypeService; + private readonly PropertyEditorCollection _propertyEditorCollection; private readonly TabsAndPropertiesMapper _tabsAndPropertiesMapper; public MediaMapDefinition(ICultureDictionary cultureDictionary, ILogger logger, CommonMapper commonMapper, IMediaService mediaService, IMediaTypeService mediaTypeService, - ILocalizedTextService localizedTextService) + ILocalizedTextService localizedTextService, PropertyEditorCollection propertyEditorCollection) { _logger = logger; _commonMapper = commonMapper; _mediaService = mediaService; _mediaTypeService = mediaTypeService; + _propertyEditorCollection = propertyEditorCollection; _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(cultureDictionary, localizedTextService); } @@ -59,7 +62,7 @@ namespace Umbraco.Web.Models.Mapping target.Id = source.Id; target.IsChildOfListView = DermineIsChildOfListView(source); target.Key = source.Key; - target.MediaLink = string.Join(",", source.GetUrls(Current.Configs.Settings().Content, _logger)); + target.MediaLink = string.Join(",", source.GetUrls(Current.Configs.Settings().Content, _logger, _propertyEditorCollection)); target.Name = source.Name; target.Owner = _commonMapper.GetOwner(source, context); target.ParentId = source.ParentId; diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 37f86ae61e..ee241f3245 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -26,12 +26,13 @@ namespace Umbraco.Web.Models.Mapping mapper.Define((source, context) => new MemberBasic(), Map); mapper.Define((source, context) => new MemberGroupDisplay(), Map); mapper.Define((source, context) => new ContentPropertyCollectionDto(), Map); - } + } // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsChildOfListView // Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture private void Map(IMember source, MemberDisplay target, MapperContext context) { + target.ContentApps = _commonMapper.GetContentApps(source); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; target.ContentTypeName = source.ContentType.Name; diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index c25a42195e..b7a3a5bb02 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -212,7 +212,8 @@ namespace Umbraco.Web.Mvc markupToInject = string.Format(Current.Configs.Settings().Content.PreviewBadge, Current.IOHelper.ResolveUrl(Current.Configs.Global().UmbracoPath), - Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Url?.PathAndQuery)); + Server.UrlEncode(Current.UmbracoContext.HttpContext.Request.Url?.PathAndQuery), + Current.UmbracoContext.PublishedRequest.PublishedContent.Id); } else { diff --git a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs index 756ca7f05c..0803941a70 100644 --- a/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web/Mvc/ValidateMvcAngularAntiForgeryTokenAttribute.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Mvc var userIdentity = filterContext.HttpContext.User.Identity as ClaimsIdentity; if (userIdentity != null) { - //if there is not CookiePath claim, then exist + //if there is not CookiePath claim, then exit if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) { base.OnActionExecuting(filterContext); diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index e30a0899c7..30e233dee3 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.PropertyEditors "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] - public class FileUploadPropertyEditor : DataEditor + public class FileUploadPropertyEditor : DataEditor, IDataEditorWithMediaPath { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSection; @@ -49,6 +49,8 @@ namespace Umbraco.Web.PropertyEditors return editor; } + public string GetMediaPath(object value) => value?.ToString(); + /// /// Gets a value indicating whether a property is an upload field. /// diff --git a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs index 7c9a549aef..74ea517fa2 100644 --- a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors public JObject Items { get; set; } // TODO: Make these strongly typed, for now this works though - [ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] + [ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration", HideLabel = true)] public JObject Rte { get; set; } [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index cfe32a1095..8151e312a8 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.PropertyEditors HideLabel = false, Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-crop")] - public class ImageCropperPropertyEditor : DataEditor + public class ImageCropperPropertyEditor : DataEditor, IDataEditorWithMediaPath { private readonly IMediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; @@ -54,6 +54,8 @@ namespace Umbraco.Web.PropertyEditors _autoFillProperties = new UploadAutoFillProperties(_mediaFileSystem, logger, _contentSettings); } + public string GetMediaPath(object value) => GetFileSrcFromPropertyValue(value, out _, false); + /// /// Creates the corresponding property value editor. /// @@ -138,8 +140,9 @@ namespace Umbraco.Web.PropertyEditors /// /// /// The deserialized value + /// Should the path returned be the application relative path /// - private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue) + private string GetFileSrcFromPropertyValue(object propVal, out JObject deserializedValue, bool relative = true) { deserializedValue = null; if (propVal == null || !(propVal is string str)) return null; @@ -147,7 +150,7 @@ namespace Umbraco.Web.PropertyEditors deserializedValue = GetJObject(str, true); if (deserializedValue?["src"] == null) return null; var src = deserializedValue["src"].Value(); - return _mediaFileSystem.GetRelativePath(src); + return relative ? _mediaFileSystem.GetRelativePath(src) : src; } /// diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 03f931c18a..19be13b762 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -238,11 +238,24 @@ namespace Umbraco.Web.PublishedCache.NuCache var lockInfo = new WriteLockInfo(); try { - Lock(lockInfo); + try + { + // Trying to lock could throw exceptions so always make sure to clean up. + Lock(lockInfo); + } + finally + { + try + { + _localDb?.Dispose(); + } + catch { /* TBD: May already be throwing so don't throw again */} + finally + { + _localDb = null; + } + } - if (_localDb == null) return; - _localDb.Dispose(); - _localDb = null; } finally { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index 1edb20633f..4515e235ad 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -207,7 +207,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource VersionDate = dto.EditVersionDate, WriterId = dto.EditWriterId, Properties = nested.PropertyData, - CultureInfos = nested.CultureData + CultureInfos = nested.CultureData, + UrlSegment = nested.UrlSegment }; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index ee5e9a2ee2..596fc46874 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -61,7 +61,8 @@ namespace Umbraco.Web.PublishedCache.NuCache private BPlusTree _localContentDb; private BPlusTree _localMediaDb; - private bool _localDbExists; + private bool _localContentDbExists; + private bool _localMediaDbExists; // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching @@ -135,9 +136,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // 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}", _localDbExists); - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, Current.PublishedModelFactory, _localContentDb); - _logger.Info("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localDbExists); + _logger.Info("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists); + _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, Current.PublishedModelFactory, _localContentDb); + _logger.Info("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists); _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, logger, Current.PublishedModelFactory, _localMediaDb); } else @@ -178,14 +179,15 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - var localContentDbExists = File.Exists(localContentDbPath); - var localMediaDbExists = File.Exists(localMediaDbPath); - _localDbExists = localContentDbExists && localMediaDbExists; - // if both local databases exist then GetTree will open them, else new databases will be created - _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); - _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); - _logger.Info("Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}", localContentDbExists, localMediaDbExists); + _localContentDbExists = File.Exists(localContentDbPath); + _localMediaDbExists = File.Exists(localMediaDbPath); + + // if both local databases exist then GetTree will open them, else new databases will be created + _localContentDb = BTree.GetTree(localContentDbPath, _localContentDbExists); + _localMediaDb = BTree.GetTree(localMediaDbPath, _localMediaDbExists); + + _logger.Info("Registered with MainDom, localContentDbExists? {LocalContentDbExists}, localMediaDbExists? {LocalMediaDbExists}", _localContentDbExists, _localMediaDbExists); } /// @@ -218,11 +220,15 @@ namespace Umbraco.Web.PublishedCache.NuCache try { - if (_localDbExists) + if (_localContentDbExists) { okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); if (!okContent) _logger.Warn("Loading content from local db raised warnings, will reload from database."); + } + + if (_localMediaDbExists) + { okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); if (!okMedia) _logger.Warn("Loading media from local db raised warnings, will reload from database."); diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index 02dc4ebf29..89abde0576 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -1,7 +1,7 @@ using System; -using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.Routing { @@ -10,13 +10,26 @@ namespace Umbraco.Web.Routing /// public class DefaultMediaUrlProvider : IMediaUrlProvider { + private readonly PropertyEditorCollection _propertyEditors; + + public DefaultMediaUrlProvider(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + } + + [Obsolete("Use the constructor with all parameters instead")] + public DefaultMediaUrlProvider() : this(Current.PropertyEditors) + { + } + /// public virtual UrlInfo GetMediaUrl(UmbracoContext umbracoContext, IPublishedContent content, - string propertyAlias, - UrlMode mode, string culture, Uri current) + string propertyAlias, UrlMode mode, string culture, Uri current) { var prop = content.GetProperty(propertyAlias); - var value = prop?.GetValue(culture); + + // get the raw source value since this is what is used by IDataEditorWithMediaPath for processing + var value = prop?.GetSourceValue(culture); if (value == null) { return null; @@ -25,15 +38,10 @@ namespace Umbraco.Web.Routing var propType = prop.PropertyType; string path = null; - switch (propType.EditorAlias) + if (_propertyEditors.TryGet(propType.EditorAlias, out var editor) + && editor is IDataEditorWithMediaPath dataEditor) { - case Constants.PropertyEditors.Aliases.UploadField: - path = value.ToString(); - break; - case Constants.PropertyEditors.Aliases.ImageCropper: - //get the url from the json format - path = value is ImageCropperValue stronglyTyped ? stronglyTyped.Src : value.ToString(); - break; + path = dataEditor.GetMediaPath(value); } var url = AssembleUrl(path, current, mode); diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index fc66b6502e..9de6c94638 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -1,4 +1,5 @@ -using System.Web; +using System; +using System.Web; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; @@ -37,8 +38,9 @@ namespace Umbraco.Web.Runtime IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, IDbProviderFactoryCreator dbProviderFactoryCreator, - IBulkSqlInsertProvider bulkSqlInsertProvider): - base(configs, umbracoVersion, ioHelper, logger, profiler ,new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, bulkSqlInsertProvider) + IBulkSqlInsertProvider bulkSqlInsertProvider, + IMainDom mainDom): + base(configs, umbracoVersion, ioHelper, logger, profiler ,new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, bulkSqlInsertProvider, mainDom) { _umbracoApplication = umbracoApplication; diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 47e97593f0..c0475b1f79 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -199,7 +199,7 @@ namespace Umbraco.Web.Scheduling { lock (_locker) { - var task = _runningTask ?? Task.FromResult(0); + var task = _runningTask ?? Task.CompletedTask; return new ThreadingTaskImmutable(task); } } @@ -211,8 +211,9 @@ namespace Umbraco.Web.Scheduling /// An awaitable object. /// /// Used to wait until the runner has terminated. - /// This is for unit tests and should not be used otherwise. In most cases when the runner - /// has terminated, the application domain is going down and it is not the right time to do things. + /// + /// The only time the runner will be terminated is by the Hosting Environment when the application is being shutdown. + /// /// internal ThreadingTaskImmutable TerminatedAwaitable { @@ -338,29 +339,37 @@ namespace Umbraco.Web.Scheduling if (_isRunning == false) return; // done already } + var hasTasks = TaskCount > 0; + + if (!force && hasTasks) + _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); + // complete the queue // will stop waiting on the queue or on a latch _tasks.Complete(); if (force) { - // we must bring everything down, now - Thread.Sleep(100); // give time to Complete() + // we must bring everything down, now lock (_locker) { // was Complete() enough? - if (_isRunning == false) return; + // if _tasks.Complete() ended up triggering code to stop the runner and reset + // the _isRunning flag, then there's no need to initiate a cancel on the cancelation token. + if (_isRunning == false) + return; } + // try to cancel running async tasks (cannot do much about sync tasks) // break latched tasks // stop processing the queue - _shutdownTokenSource.Cancel(false); // false is the default - _shutdownTokenSource.Dispose(); + _shutdownTokenSource?.Cancel(false); // false is the default + _shutdownTokenSource?.Dispose(); _shutdownTokenSource = null; } // tasks in the queue will be executed... - if (wait == false) return; + if (!wait) return; _runningTask?.Wait(CancellationToken.None); // wait for whatever is running to end... } @@ -428,7 +437,7 @@ namespace Umbraco.Web.Scheduling lock (_locker) { // deal with race condition - if (_shutdownToken.IsCancellationRequested == false && _tasks.Count > 0) continue; + if (_shutdownToken.IsCancellationRequested == false && TaskCount > 0) continue; // if we really have nothing to do, stop _logger.Debug("{LogPrefix} Stopping", _logPrefix); @@ -453,7 +462,7 @@ namespace Umbraco.Web.Scheduling // if KeepAlive is false then don't block, exit if there is // no task in the buffer - yes, there is a race condition, which // we'll take care of - if (_options.KeepAlive == false && _tasks.Count == 0) + if (_options.KeepAlive == false && TaskCount == 0) return null; try @@ -503,15 +512,19 @@ namespace Umbraco.Web.Scheduling // returns the task that completed // - latched.Latch completes when the latch releases // - _tasks.Completion completes when the runner completes - // - tokenTaskSource.Task completes when this task, or the whole runner, is cancelled + // - tokenTaskSource.Task completes when this task, or the whole runner is cancelled var task = await Task.WhenAny(latched.Latch, _tasks.Completion, tokenTaskSource.Task); // ok to run now if (task == latched.Latch) return bgTask; + // we are shutting down if the _tasks.Complete(); was called or the shutdown token was cancelled + var isShuttingDown = _shutdownToken.IsCancellationRequested || task == _tasks.Completion; + // if shutting down, return the task only if it runs on shutdown - if (_shutdownToken.IsCancellationRequested == false && latched.RunsOnShutdown) return bgTask; + if (isShuttingDown && latched.RunsOnShutdown) + return bgTask; // else, either it does not run on shutdown or it's been cancelled, dispose latched.Dispose(); @@ -578,17 +591,18 @@ namespace Umbraco.Web.Scheduling // triggers when the hosting environment requests that the runner terminates internal event TypedEventHandler, EventArgs> Terminating; - // triggers when the runner has terminated (no task can be added, no task is running) + // triggers when the hosting environment has terminated (no task can be added, no task is running) internal event TypedEventHandler, EventArgs> Terminated; private void OnEvent(TypedEventHandler, EventArgs> handler, string name) { - if (handler == null) return; OnEvent(handler, name, EventArgs.Empty); } private void OnEvent(TypedEventHandler, TArgs> handler, string name, TArgs e) { + _logger.Debug("{LogPrefix} OnEvent {EventName}", _logPrefix, name); + if (handler == null) return; try @@ -664,17 +678,16 @@ namespace Umbraco.Web.Scheduling #endregion + #region IRegisteredObject.Stop + /// - /// Requests a registered object to un-register. + /// Used by IRegisteredObject.Stop and shutdown on threadpool threads to not block shutdown times. /// - /// true to indicate the registered object should un-register from the hosting - /// environment before returning; otherwise, false. - /// - /// "When the application manager needs to stop a registered object, it will call the Stop method." - /// The application manager will call the Stop method to ask a registered object to un-register. During - /// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method. - /// - public void Stop(bool immediate) + /// + /// + /// An awaitable Task that is used to handle the shutdown. + /// + internal Task StopInternal(bool immediate) { // the first time the hosting environment requests that the runner terminates, // raise the Terminating event - that could be used to prevent any process that @@ -693,33 +706,90 @@ namespace Umbraco.Web.Scheduling if (onTerminating) OnEvent(Terminating, "Terminating"); - if (immediate == false) + // Run the Stop commands on another thread since IRegisteredObject.Stop calls are called sequentially + // with a single aspnet thread during shutdown and we don't want to delay other calls to IRegisteredObject.Stop. + if (!immediate) { - // The Stop method is first called with the immediate parameter set to false. The object can either complete - // processing, call the UnregisterObject method, and then return or it can return immediately and complete - // processing asynchronously before calling the UnregisterObject method. + return Task.Run(StopInitial, CancellationToken.None); + } + else + { + lock (_locker) + { + if (_terminated) return Task.CompletedTask; + return Task.Run(StopImmediate, CancellationToken.None); + } + } + } - _logger.Info("{LogPrefix} Waiting for tasks to complete", _logPrefix); + /// + /// Requests a registered object to un-register. + /// + /// true to indicate the registered object should un-register from the hosting + /// environment before returning; otherwise, false. + /// + /// "When the application manager needs to stop a registered object, it will call the Stop method." + /// The application manager will call the Stop method to ask a registered object to un-register. During + /// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method. + /// + public void Stop(bool immediate) => StopInternal(immediate); + + /// + /// Called when immediate == false for IRegisteredObject.Stop(bool immediate) + /// + /// + /// Called on a threadpool thread + /// + private void StopInitial() + { + // immediate == false when the app is trying to wind down, immediate == true will be called either: + // after a call with immediate == false or if the app is not trying to wind down and needs to immediately stop. + // So Stop may be called twice or sometimes only once. + + try + { Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait - + } + finally + { // raise the completed event only after the running threading task has completed lock (_locker) { if (_runningTask != null) - _runningTask.ContinueWith(_ => Terminate(false)); + _runningTask.ContinueWith(_ => StopImmediate()); else - Terminate(false); + StopImmediate(); } } - else - { - // If the registered object does not complete processing before the application manager's time-out - // period expires, the Stop method is called again with the immediate parameter set to true. When the - // immediate parameter is true, the registered object must call the UnregisterObject method before returning; - // otherwise, its registration will be removed by the application manager. - _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); + // If the shutdown token was not canceled in the Shutdown call above, it means there was still tasks + // being processed, in which case we'll give it a couple seconds + if (!_shutdownToken.IsCancellationRequested) + { + // If we are called with immediate == false, wind down above and then shutdown within 2 seconds, + // we want to shut down the app as quick as possible, if we wait until immediate == true, this can + // take a very long time since immediate will only be true when a new request is received on the new + // appdomain (or another iis timeout occurs ... which can take some time). + Thread.Sleep(2000); //we are already on a threadpool thread + StopImmediate(); + } + } + + /// + /// Called when immediate == true for IRegisteredObject.Stop(bool immediate) + /// + /// + /// Called on a threadpool thread + /// + private void StopImmediate() + { + _logger.Info("{LogPrefix} Canceling tasks", _logPrefix); + try + { Shutdown(true, true); // cancel all tasks, wait for the current one to end + } + finally + { Terminate(true); } } @@ -732,7 +802,13 @@ namespace Umbraco.Web.Scheduling // raise the Terminated event // complete the awaitable completion source, if any - HostingEnvironment.UnregisterObject(this); + if (immediate) + { + //only unregister when it's the final call, else we won't be notified of the final call + HostingEnvironment.UnregisterObject(this); + } + + if (_terminated) return; // already taken care of TaskCompletionSource terminatedSource; lock (_locker) @@ -747,7 +823,9 @@ namespace Umbraco.Web.Scheduling OnEvent(Terminated, "Terminated"); - terminatedSource.SetResult(0); + terminatedSource.TrySetResult(0); } + + #endregion } } diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index c07430df04..9a22c59566 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -3,6 +3,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Sync; @@ -11,14 +13,16 @@ namespace Umbraco.Web.Scheduling internal class KeepAlive : RecurringTaskBase { private readonly IRuntimeState _runtime; + private readonly IKeepAliveSection _keepAliveSection; private readonly IProfilingLogger _logger; private static HttpClient _httpClient; public KeepAlive(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, - IRuntimeState runtime, IProfilingLogger logger) + IRuntimeState runtime, IKeepAliveSection keepAliveSection, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { _runtime = runtime; + _keepAliveSection = keepAliveSection; _logger = logger; if (_httpClient == null) _httpClient = new HttpClient(); @@ -46,25 +50,27 @@ namespace Umbraco.Web.Scheduling using (_logger.DebugDuration("Keep alive executing", "Keep alive complete")) { - string umbracoAppUrl = null; - + var keepAlivePingUrl = _keepAliveSection.KeepAlivePingUrl; try { - umbracoAppUrl = _runtime.ApplicationUrl.ToString(); - if (umbracoAppUrl.IsNullOrWhiteSpace()) + if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}")) { - _logger.Warn("No url for service (yet), skip."); - return true; // repeat + var umbracoAppUrl = _runtime.ApplicationUrl.ToString(); + if (umbracoAppUrl.IsNullOrWhiteSpace()) + { + _logger.Warn("No umbracoApplicationUrl for service (yet), skip."); + return true; // repeat + } + + keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd('/')); } - var url = umbracoAppUrl.TrimEnd('/') + "/api/keepalive/ping"; - - var request = new HttpRequestMessage(HttpMethod.Get, url); + var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl); var result = await _httpClient.SendAsync(request, token); } catch (Exception ex) { - _logger.Error(ex, "Keep alive failed (at '{UmbracoAppUrl}').", umbracoAppUrl); + _logger.Error(ex, "Keep alive failed (at '{keepAlivePingUrl}').", keepAlivePingUrl); } } diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs index 7ca58f667a..b537146874 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -99,7 +99,11 @@ namespace Umbraco.Web.Scheduling var tasks = new List(); - tasks.Add(RegisterKeepAlive()); + if (settings.KeepAlive.DisableKeepAliveTask == false) + { + tasks.Add(RegisterKeepAlive(settings.KeepAlive)); + } + tasks.Add(RegisterScheduledPublishing()); tasks.Add(RegisterLogScrubber(settings)); tasks.Add(RegisterTempFileCleanup()); @@ -112,11 +116,11 @@ namespace Umbraco.Web.Scheduling }); } - private IBackgroundTask RegisterKeepAlive() + private IBackgroundTask RegisterKeepAlive(IKeepAliveSection keepAliveSection) { // ping/keepalive // on all servers - var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, _logger); + var task = new KeepAlive(_keepAliveRunner, DefaultDelayMilliseconds, FiveMinuteMilliseconds, _runtime, keepAliveSection, _logger); _keepAliveRunner.TryAdd(task); return task; } diff --git a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs index e56e35ba2a..ee28d6d18d 100644 --- a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs +++ b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs @@ -44,6 +44,9 @@ namespace Umbraco.Web.Security : Guid.NewGuid(); backOfficeIdentity.SessionId = session.ToString(); + + //since it is a cookie-based authentication add that claim + backOfficeIdentity.AddClaim(new Claim(ClaimTypes.CookiePath, "/", ClaimValueTypes.String, UmbracoBackOfficeIdentity.Issuer, UmbracoBackOfficeIdentity.Issuer, backOfficeIdentity)); } base.ResponseSignIn(context); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index faa6f56d2b..9b43f54130 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -454,8 +454,8 @@ namespace Umbraco.Web.Trees internal IEnumerable GetAllowedUserMenuItemsForNode(IUmbracoEntity dd) { - var permission = Services.UserService.GetPermissions(Security.CurrentUser, dd.Path); - return Current.Actions.FromEntityPermission(permission).Select(x => new MenuItem(x)); + var permissionsForPath = Services.UserService.GetPermissionsForPath(Security.CurrentUser, dd.Path).GetAllPermissions(); + return Current.Actions.GetByLetters(permissionsForPath).Select(x => new MenuItem(x)); } /// diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index bd80f63897..2046baf2d3 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -2,7 +2,10 @@ using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Trees; +using Umbraco.Web.Search; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Trees @@ -10,8 +13,15 @@ namespace Umbraco.Web.Trees [CoreTree] [UmbracoTreeAuthorize(Constants.Trees.MemberTypes)] [Tree(Constants.Applications.Settings, Constants.Trees.MemberTypes, SortOrder = 2, TreeGroup = Constants.Trees.Groups.Settings)] - public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase + public class MemberTypeTreeController : MemberTypeAndGroupTreeControllerBase, ISearchableTree { + private readonly UmbracoTreeSearcher _treeSearcher; + + public MemberTypeTreeController(UmbracoTreeSearcher treeSearcher) + { + _treeSearcher = treeSearcher; + } + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) { var root = base.CreateRootNode(queryStrings); @@ -25,5 +35,9 @@ namespace Umbraco.Web.Trees .OrderBy(x => x.Name) .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, Constants.Icons.MemberType, false)); } + + public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + => _treeSearcher.EntitySearch(UmbracoObjectTypes.MemberType, query, pageSize, pageIndex, out totalFound, searchFrom); + } } diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 255c2a80c1..3943e3d17f 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -22,8 +22,9 @@ namespace Umbraco.Web var dbProviderFactoryCreator = new UmbracoDbProviderFactoryCreator(connectionStringConfig.ProviderName); var bulkSqlInsertProvider = connectionStringConfig.ProviderName == Constants.DbProviderNames.SqlCe ? (IBulkSqlInsertProvider) new SqlCeBulkSqlInsertProvider() : new SqlServerBulkSqlInsertProvider(); + var mainDom = new MainDom(logger, hostingEnvironment); - return new WebRuntime(this, configs, umbracoVersion, ioHelper, logger, profiler, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, bulkSqlInsertProvider); + return new WebRuntime(this, configs, umbracoVersion, ioHelper, logger, profiler, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, bulkSqlInsertProvider, mainDom); } /// diff --git a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs index 0abdfb5d2f..f147a2a4cb 100644 --- a/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ValidateAngularAntiForgeryTokenAttribute.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.WebApi.Filters var userIdentity = ((ApiController) actionContext.ControllerContext.Controller).User.Identity as ClaimsIdentity; if (userIdentity != null) { - //if there is not CookiePath claim, then exist + //if there is not CookiePath claim, then exit if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false) { base.OnActionExecuting(actionContext);