diff --git a/.gitignore b/.gitignore index d8c3f27d5a..2a73537662 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,13 @@ build/hooks/ build/temp/ +# Acceptance tests +cypress.env.json +/src/Umbraco.Tests.AcceptanceTest/cypress/support/chainable.ts +/src/Umbraco.Tests.AcceptanceTest/package-lock.json +/src/Umbraco.Tests.AcceptanceTest/cypress/videos/ +/src/Umbraco.Tests.AcceptanceTest/cypress/screenshots/ + # eof /src/Umbraco.Web.UI.Client/TESTS-*.xml diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 969ac5be3d..cfab130f0c 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -33,7 +33,7 @@ - + diff --git a/build/build.ps1 b/build/build.ps1 index 6e124d1508..3ba347a6dc 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -381,7 +381,7 @@ { Write-Host "Restore NuGet" Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log" - $params = "-Source", $nugetsourceUmbraco + $params = "-Source", $nugetsourceUmbraco &$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params if (-not $?) { throw "Failed to restore NuGet packages." } }) @@ -535,6 +535,7 @@ # run if (-not $get) { +cd if ($command.Length -eq 0) { $command = @( "Build" ) diff --git a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs index 7e3ff80690..dce3d85840 100644 --- a/src/Umbraco.Configuration/Legacy/SmtpSettings.cs +++ b/src/Umbraco.Configuration/Legacy/SmtpSettings.cs @@ -1,3 +1,4 @@ +using System.Net.Mail; using Umbraco.Core.Configuration; namespace Umbraco.Configuration @@ -8,5 +9,10 @@ namespace Umbraco.Configuration public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public SmtpDeliveryMethod DeliveryMethod { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } } } diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index 4b30813bd5..e4995cfb0b 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net.Mail; using Microsoft.Extensions.Configuration; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -72,10 +73,10 @@ namespace Umbraco.Configuration.Models _configuration.GetValue(Prefix + "NoNodesViewPath", "~/config/splashes/NoNodes.cshtml"); public bool IsSmtpServerConfigured => - _configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")?.GetChildren().Any() ?? false; + _configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")?.GetChildren().Any() ?? false; public ISmtpSettings SmtpSettings => - new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")); + new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")); private class SmtpSettingsImpl : ISmtpSettings { @@ -90,6 +91,11 @@ namespace Umbraco.Configuration.Models public string Host => _configurationSection.GetValue("Host"); public int Port => _configurationSection.GetValue("Port"); public string PickupDirectoryLocation => _configurationSection.GetValue("PickupDirectoryLocation"); + public SmtpDeliveryMethod DeliveryMethod => _configurationSection.GetValue("DeliveryMethod"); + + public string Username => _configurationSection.GetValue("Username"); + + public string Password => _configurationSection.GetValue("Password"); } } } diff --git a/src/Umbraco.Core/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/ClaimsIdentityExtensions.cs new file mode 100644 index 0000000000..53fde97312 --- /dev/null +++ b/src/Umbraco.Core/ClaimsIdentityExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Security.Claims; +using System.Security.Principal; + +namespace Umbraco.Core +{ + public static class ClaimsIdentityExtensions + { + public static string GetUserId(this IIdentity identity) + { + if (identity == null) throw new ArgumentNullException(nameof(identity)); + + string userId = null; + if (identity is ClaimsIdentity claimsIdentity) + { + userId = claimsIdentity.FindFirstValue(ClaimTypes.NameIdentifier) + ?? claimsIdentity.FindFirstValue("sub"); + } + + return userId; + } + + public static string GetUserName(this IIdentity identity) + { + if (identity == null) throw new ArgumentNullException(nameof(identity)); + + string username = null; + if (identity is ClaimsIdentity claimsIdentity) + { + username = claimsIdentity.FindFirstValue(ClaimTypes.Name) + ?? claimsIdentity.FindFirstValue("preferred_username"); + } + + return username; + } + + public static string FindFirstValue(this ClaimsIdentity identity, string claimType) + { + if (identity == null) throw new ArgumentNullException(nameof(identity)); + + return identity.FindFirst(claimType)?.Value; + } + } +} diff --git a/src/Umbraco.Core/Configuration/ISmtpSettings.cs b/src/Umbraco.Core/Configuration/ISmtpSettings.cs index c2fb4b2dbe..ea42ae5567 100644 --- a/src/Umbraco.Core/Configuration/ISmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/ISmtpSettings.cs @@ -1,3 +1,5 @@ +using System.Net.Mail; + namespace Umbraco.Core.Configuration { public interface ISmtpSettings @@ -6,5 +8,8 @@ namespace Umbraco.Core.Configuration string Host { get; } int Port{ get; } string PickupDirectoryLocation { get; } + SmtpDeliveryMethod DeliveryMethod { get; } + string Username { get; } + string Password { get; } } } diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index b1efd782fa..5602b99e2d 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -39,6 +39,16 @@ /// The route name of the page shown when Umbraco has no published content. /// public const string NoContentRouteName = "umbraco-no-content"; + + /// + /// The claim type for the ASP.NET Identity security stamp + /// + public const string SecurityStampClaimType = "AspNet.Identity.SecurityStamp"; + + /// + /// The default authentication type used for remembering that 2FA is not needed on next login + /// + public const string TwoFactorRememberBrowserCookie = "TwoFactorRememberBrowser"; } } } diff --git a/src/Umbraco.Core/FactoryExtensions.cs b/src/Umbraco.Core/FactoryExtensions.cs index 8514525417..8ae2f76af3 100644 --- a/src/Umbraco.Core/FactoryExtensions.cs +++ b/src/Umbraco.Core/FactoryExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Umbraco.Core.Composing; @@ -77,15 +78,28 @@ namespace Umbraco.Core var ctorParameters = ctor.GetParameters(); var ctorArgs = new object[ctorParameters.Length]; + var availableArgs = new List(args); var i = 0; foreach (var parameter in ctorParameters) { // no! IsInstanceOfType is not ok here // ReSharper disable once UseMethodIsInstanceOfType - var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); - ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType); + var idx = availableArgs.FindIndex(a => parameter.ParameterType.IsAssignableFrom(a.GetType())); + if(idx >= 0) + { + // Found a suitable supplied argument + ctorArgs[i++] = availableArgs[idx]; + + // A supplied argument can be used at most once + availableArgs.RemoveAt(idx); + } + else + { + // None of the provided arguments is suitable: get an instance from the factory + ctorArgs[i++] = factory.GetInstance(parameter.ParameterType); + } } return ctor.Invoke(ctorArgs); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs index d180a68a2c..5ff1744ea2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs @@ -54,5 +54,8 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } + + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } } } diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs index 225e29a8a1..f27feba8cf 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs @@ -16,6 +16,11 @@ /// public string Culture { get; set; } + /// + /// When dealing with content variants, this is the segment for the variant + /// + public string Segment { get; set; } + /// /// An array of metadata that is parsed out from the file info posted to the server which is set on the client. /// diff --git a/src/Umbraco.Core/Models/RelationTypeExtensions.cs b/src/Umbraco.Core/Models/RelationTypeExtensions.cs new file mode 100644 index 0000000000..4d9d6856cb --- /dev/null +++ b/src/Umbraco.Core/Models/RelationTypeExtensions.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Core.Models +{ + public static class RelationTypeExtensions + { + public static bool IsSystemRelationType(this IRelationType relationType) => + relationType.Alias == Constants.Conventions.RelationTypes.RelatedDocumentAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelatedMediaAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + } +} diff --git a/src/Umbraco.Core/Models/Trees/MenuItem.cs b/src/Umbraco.Core/Models/Trees/MenuItem.cs index 9d4c76eea1..094c6b24ff 100644 --- a/src/Umbraco.Core/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Core/Models/Trees/MenuItem.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Actions; +using System.Threading; namespace Umbraco.Web.Models.Trees { @@ -28,12 +29,15 @@ namespace Umbraco.Web.Models.Trees Name = name; } - public MenuItem(string alias, ILocalizedTextService textService) : this() { + var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{alias}_description", out var textDescription); + Alias = alias; Name = textService.Localize($"actions/{Alias}"); + TextDescription = textDescription; } /// @@ -74,6 +78,9 @@ namespace Umbraco.Web.Models.Trees [Required] public string Alias { get; set; } + [DataMember(Name = "textDescription")] + public string TextDescription { get; set; } + /// /// Ensures a menu separator will exist before this menu item /// diff --git a/src/Umbraco.Core/Trees/MenuItemList.cs b/src/Umbraco.Core/Trees/MenuItemList.cs index 546fa0390f..1410575fdb 100644 --- a/src/Umbraco.Core/Trees/MenuItemList.cs +++ b/src/Umbraco.Core/Trees/MenuItemList.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using Umbraco.Core.Services; using Umbraco.Web.Actions; @@ -50,14 +51,17 @@ namespace Umbraco.Web.Models.Trees var item = _actionCollection.GetAction(); if (item == null) return null; + var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription); + var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}")) { SeparatorBefore = hasSeparator, - OpensDialog = opensDialog + OpensDialog = opensDialog, + TextDescription = textDescription, }; return menuItem; } - } } diff --git a/src/Umbraco.Infrastructure/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Infrastructure/Models/ContentEditing/ContentTypeSave.cs index b1d24c5fd2..a93be56846 100644 --- a/src/Umbraco.Infrastructure/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Infrastructure/Models/ContentEditing/ContentTypeSave.cs @@ -69,6 +69,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } + //Tabs [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } diff --git a/src/Umbraco.Infrastructure/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Infrastructure/Models/ContentEditing/ContentVariationDisplay.cs index cf871142a6..cb1834980e 100644 --- a/src/Umbraco.Infrastructure/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Infrastructure/Models/ContentEditing/ContentVariationDisplay.cs @@ -24,6 +24,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name", IsRequired = true)] public string Name { get; set; } + [DataMember(Name = "displayName")] + public string DisplayName { get; set; } + /// /// Defines the tabs containing display properties /// diff --git a/src/Umbraco.Infrastructure/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Infrastructure/Models/ContentEditing/DocumentTypeDisplay.cs index 6b82f74ca7..9467033aec 100644 --- a/src/Umbraco.Infrastructure/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Infrastructure/Models/ContentEditing/DocumentTypeDisplay.cs @@ -20,9 +20,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "defaultTemplate")] public EntityBasic DefaultTemplate { get; set; } - + [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } + } } diff --git a/src/Umbraco.Infrastructure/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Infrastructure/Models/ContentEditing/RelationTypeDisplay.cs index 49b0e15e6b..1d31f8a0de 100644 --- a/src/Umbraco.Infrastructure/Models/ContentEditing/RelationTypeDisplay.cs +++ b/src/Umbraco.Infrastructure/Models/ContentEditing/RelationTypeDisplay.cs @@ -13,6 +13,9 @@ namespace Umbraco.Web.Models.ContentEditing Notifications = new List(); } + [DataMember(Name = "isSystemRelationType")] + public bool IsSystemRelationType { get; set; } + /// /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) /// diff --git a/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs index 2e985bef07..37bd8da021 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/ContentTypeMapDefinition.cs @@ -132,6 +132,7 @@ namespace Umbraco.Web.Models.Mapping MapTypeToDisplayBase(source, target); target.AllowCultureVariant = source.VariesByCulture(); + target.AllowSegmentVariant = source.VariesBySegment(); //sync templates target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates); @@ -245,6 +246,7 @@ namespace Umbraco.Web.Models.Mapping target.ValidationRegExp = source.Validation.Pattern; target.ValidationRegExpMessage = source.Validation.PatternMessage; target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); if (source.Id > 0) target.Id = source.Id; @@ -356,6 +358,7 @@ namespace Umbraco.Web.Models.Mapping { target.Alias = source.Alias; target.AllowCultureVariant = source.AllowCultureVariant; + target.AllowSegmentVariant = source.AllowSegmentVariant; target.DataTypeId = source.DataTypeId; target.DataTypeKey = source.DataTypeKey; target.Description = source.Description; @@ -372,6 +375,7 @@ namespace Umbraco.Web.Models.Mapping { target.Alias = source.Alias; target.AllowCultureVariant = source.AllowCultureVariant; + target.AllowSegmentVariant = source.AllowSegmentVariant; target.DataTypeId = source.DataTypeId; target.DataTypeKey = source.DataTypeKey; target.Description = source.Description; @@ -417,6 +421,7 @@ namespace Umbraco.Web.Models.Mapping if (!(target is IMemberType)) { target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); } // handle property groups and property types diff --git a/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs index 10a19287e7..1a852f7c72 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/ContentVariantMapper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Umbraco.Core; using Umbraco.Core.Mapping; @@ -13,10 +14,12 @@ namespace Umbraco.Web.Models.Mapping public class ContentVariantMapper { private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; - public ContentVariantMapper(ILocalizationService localizationService) + public ContentVariantMapper(ILocalizationService localizationService, ILocalizedTextService localizedTextService) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); } public IEnumerable Map(IContent source, MapperContext context) @@ -113,11 +116,19 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - /// Returns all segments assigned to the content including 'null' values + /// Returns all segments assigned to the content including the default `null` segment. /// private IEnumerable GetSegments(IContent content) { - return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct(); + // The default segment (null) is always there, + // even when there is no property data at all yet + var segments = new List { null }; + + // Add actual segments based on the property values + segments.AddRange(content.Properties.SelectMany(p => p.Values.Select(v => v.Segment))); + + // Do not return a segment more than once + return segments.Distinct(); } private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment) @@ -130,8 +141,31 @@ namespace Umbraco.Web.Models.Mapping variantDisplay.Segment = segment; variantDisplay.Language = language; variantDisplay.Name = content.GetCultureName(language?.IsoCode); + variantDisplay.DisplayName = GetDisplayName(language, segment); return variantDisplay; } + + private string GetDisplayName(Language language, string segment) + { + var isCultureVariant = language != null; + var isSegmentVariant = !segment.IsNullOrWhiteSpace(); + + if(!isCultureVariant && !isSegmentVariant) + { + return _localizedTextService.Localize("general/default"); + } + + var parts = new List(); + + if (isSegmentVariant) + parts.Add(segment); + + if (isCultureVariant) + parts.Add(language.Name); + + return string.Join(" — ", parts); + + } } } diff --git a/src/Umbraco.Infrastructure/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Infrastructure/Models/Mapping/PropertyTypeGroupMapper.cs index 5cdc9c5fa4..6cee1d9246 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/PropertyTypeGroupMapper.cs @@ -244,7 +244,8 @@ namespace Umbraco.Web.Models.Mapping SortOrder = p.SortOrder, ContentTypeId = contentType.Id, ContentTypeName = contentType.Name, - AllowCultureVariant = p.VariesByCulture() + AllowCultureVariant = p.VariesByCulture(), + AllowSegmentVariant = p.VariesBySegment() }); } diff --git a/src/Umbraco.Infrastructure/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Infrastructure/Models/Mapping/RelationMapDefinition.cs index d6ec4fd969..836b04ca69 100644 --- a/src/Umbraco.Infrastructure/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Infrastructure/Models/Mapping/RelationMapDefinition.cs @@ -39,6 +39,8 @@ namespace Umbraco.Web.Models.Mapping target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key); target.Path = "-1," + source.Id; + target.IsSystemRelationType = source.IsSystemRelationType(); + // Set the "friendly" and entity names for the parent and child object types if (source.ParentObjectType.HasValue) { diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/PropertyFactory.cs index f844d14022..b360342332 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/PropertyFactory.cs @@ -124,15 +124,16 @@ namespace Umbraco.Core.Persistence.Factories // publishing = deal with edit and published values foreach (var propertyValue in property.Values) { - var isInvariantValue = propertyValue.Culture == null; - var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null; + var isInvariantValue = propertyValue.Culture == null && propertyValue.Segment == null; + var isCultureValue = propertyValue.Culture != null; + var isSegmentValue = propertyValue.Segment != null; // deal with published value - if (propertyValue.PublishedValue != null && publishedVersionId > 0) + if ((propertyValue.PublishedValue != null || isSegmentValue) && publishedVersionId > 0) propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); // deal with edit value - if (propertyValue.EditedValue != null) + if (propertyValue.EditedValue != null || isSegmentValue) propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs index 70490af1a3..360c2be9fb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors private readonly IContentTypeService _contentTypeService; private readonly IIOHelper _ioHelper; - internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; + public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; public NestedContentPropertyEditor( ILogger logger, diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs index 19d3716e1c..34d69c2cd5 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs @@ -320,6 +320,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); diff --git a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs index a52fc652bb..3f931d6358 100644 --- a/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs @@ -3,9 +3,12 @@ using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Umbraco.Core.Configuration; +using System.Web; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -17,9 +20,10 @@ namespace Umbraco.Core.Runtime public class SqlMainDomLock : IMainDomLock { private string _lockId; - private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom"; + private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom"; private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; + private readonly IHostingEnvironment _hostingEnvironment; private IUmbracoDatabase _db; private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); @@ -28,17 +32,20 @@ namespace Umbraco.Core.Runtime private bool _hasError; private object _locker = new object(); - public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator) + public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment) { // unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer _lockId = Guid.NewGuid().ToString(); _logger = logger; + _hostingEnvironment = hostingEnvironment; _dbFactory = new UmbracoDatabaseFactory(_logger, globalSettings, connectionStrings, Constants.System.UmbracoConnectionName, new Lazy(() => new MapperCollection(Enumerable.Empty())), dbProviderFactoryCreator); + + MainDomKey = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash(); } public async Task AcquireLockAsync(int millisecondsTimeout) @@ -128,6 +135,16 @@ namespace Umbraco.Core.Runtime } + /// + /// Returns the keyvalue table key for the current server/app + /// + /// + /// The key is the the normal MainDomId which takes into account the AppDomainAppId and the physical file path of the app and this is + /// combined with the current machine name. The machine name is required because the default semaphore lock is machine wide so it implicitly + /// takes into account machine name whereas this needs to be explicitly per machine. + /// + private string MainDomKey { get; } + private void ListeningLoop() { while (true) diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index db3ae1bc25..8da86df6e8 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Umbraco.Core/EmailSender.cs b/src/Umbraco.Infrastructure/Users/EmailSender.cs similarity index 55% rename from src/Umbraco.Core/EmailSender.cs rename to src/Umbraco.Infrastructure/Users/EmailSender.cs index 5cfdd765bc..9a2f425021 100644 --- a/src/Umbraco.Core/EmailSender.cs +++ b/src/Umbraco.Infrastructure/Users/EmailSender.cs @@ -1,9 +1,12 @@ using System; +using System.Linq; using System.Net.Mail; using System.Threading.Tasks; -using Umbraco.Core.Composing; +using MimeKit; +using MimeKit.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Events; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; namespace Umbraco.Core { @@ -21,7 +24,7 @@ namespace Umbraco.Core { } - internal EmailSender(IGlobalSettings globalSettings, bool enableEvents) + public EmailSender(IGlobalSettings globalSettings, bool enableEvents) { _globalSettings = globalSettings; _enableEvents = enableEvents; @@ -45,7 +48,17 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - client.Send(message); + + client.Connect(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) + { + client.Authenticate(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + + client.Send(ConstructEmailMessage(message)); + client.Disconnect(true); } } } @@ -65,14 +78,25 @@ namespace Umbraco.Core { using (var client = new SmtpClient()) { - if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port); + + if (!(_globalSettings.SmtpSettings.Username is null && + _globalSettings.SmtpSettings.Password is null)) { - await client.SendMailAsync(message); + await client.AuthenticateAsync(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password); + } + + var mailMessage = ConstructEmailMessage(message); + if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network) + { + await client.SendAsync(mailMessage); } else { - client.Send(message); + client.Send(mailMessage); } + + await client.DisconnectAsync(true); } } } @@ -83,7 +107,7 @@ namespace Umbraco.Core /// /// We assume this is possible if either an event handler is registered or an smtp server is configured /// - internal static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured; + public static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured; /// /// returns true if an event handler has been registered @@ -103,5 +127,22 @@ namespace Umbraco.Core var handler = SendEmail; if (handler != null) handler(null, e); } + + private MimeMessage ConstructEmailMessage(MailMessage mailMessage) + { + var fromEmail = mailMessage.From?.Address; + if(string.IsNullOrEmpty(fromEmail)) + fromEmail = _globalSettings.SmtpSettings.From; + + var messageToSend = new MimeMessage + { + Subject = mailMessage.Subject, + From = { new MailboxAddress(fromEmail)}, + Body = new TextPart(mailMessage.IsBodyHtml ? TextFormat.Html : TextFormat.Plain) { Text = mailMessage.Body } + }; + messageToSend.To.AddRange(mailMessage.To.Select(x=>new MailboxAddress(x.Address))); + + return messageToSend; + } } } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs index adff28a2ba..ec5424ad9a 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs @@ -9,15 +9,28 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// internal class ContentNestedData { - [JsonProperty("properties")] + //dont serialize empty properties + [JsonProperty("pd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary PropertyData { get; set; } - [JsonProperty("cultureData")] + [JsonProperty("cd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary CultureData { get; set; } - [JsonProperty("urlSegment")] + [JsonProperty("us")] public string UrlSegment { get; set; } + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("properties")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + private Dictionary LegacyPropertyData { set { PropertyData = value; } } + + [JsonProperty("cultureData")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + private Dictionary LegacyCultureData { set { CultureData = value; } } + + [JsonProperty("urlSegment")] + private string LegacyUrlSegment { set { UrlSegment = value; } } } } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/CultureVariation.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/CultureVariation.cs index 2521d42744..b59e8c403c 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/CultureVariation.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/CultureVariation.cs @@ -8,16 +8,29 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// public class CultureVariation { - [JsonProperty("name")] + [JsonProperty("nm")] public string Name { get; set; } - [JsonProperty("urlSegment")] + [JsonProperty("us")] public string UrlSegment { get; set; } - [JsonProperty("date")] + [JsonProperty("dt")] public DateTime Date { get; set; } - [JsonProperty("isDraft")] + [JsonProperty("isd")] public bool IsDraft { get; set; } + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("name")] + private string LegacyName { set { Name = value; } } + + [JsonProperty("urlSegment")] + private string LegacyUrlSegment { set { UrlSegment = value; } } + + [JsonProperty("date")] + private DateTime LegacyDate { set { Date = value; } } + + [JsonProperty("isDraft")] + private bool LegacyIsDraft { set { IsDraft = value; } } } } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs index 9ab9401d71..cf7ab95360 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Newtonsoft.Json; namespace Umbraco.Web.PublishedCache.NuCache.DataSource @@ -8,21 +9,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource private string _culture; private string _segment; - [JsonProperty("culture")] + [DefaultValue("")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "c")] public string Culture { get => _culture; set => _culture = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null } - [JsonProperty("seg")] + [DefaultValue("")] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "s")] public string Segment { get => _segment; set => _segment = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null } - [JsonProperty("val")] + [JsonProperty("v")] public object Value { get; set; } + + + //Legacy properties used to deserialize existing nucache db entries + [JsonProperty("culture")] + private string LegacyCulture + { + set => Culture = value; + } + + [JsonProperty("seg")] + private string LegacySegment + { + set => Segment = value; + } + + [JsonProperty("val")] + private object LegacyValue + { + set => Value = value; + } } } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 7f78ecd5cf..815fe0a168 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -901,22 +901,15 @@ namespace Umbraco.Web.PublishedCache.NuCache // they require. // These can be run side by side in parallel. + using (_contentStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + } - Parallel.Invoke( - () => - { - using (_contentStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); - } - }, - () => - { - using (_mediaStore.GetScopedWriteLock(_scopeProvider)) - { - NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); - } - }); + using (_mediaStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); + } } ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); diff --git a/src/Umbraco.Tests.AcceptanceTest/README.md b/src/Umbraco.Tests.AcceptanceTest/README.md new file mode 100644 index 0000000000..e8699b0733 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/README.md @@ -0,0 +1,35 @@ +# Umbraco Acceptance Tests + +### Prerequisite +- NodeJS 12+ +- A running installed Umbraco on url: [https://localhost:44331](https://localhost:44331) (Default development port) + - Install using a `SqlServer`/`LocalDb` as the tests execute too fast for `SqlCE` to handle. +- User information in `cypress.env.json` (See [Getting started](#getting-started)) + +### Getting started +The tests is located in the project/folder named `Umbraco.Tests.AcceptanceTests`. Ensur to run `npm install` in that folder, or let your IDE do that. + +Next, it is important you create a new file in the root of the project called `cypress.env.json`. +This file is already added to `.gitignore` and can contain values that is different for each developer machine. + +The file need the following content: +``` +{ + "username": "", + "password": "" +} +``` +Replace the `` and `` placeholders with correct info. + + + +### Executing tests + +There exists two npm scripts, that can be used to execute the test. + +1. `npm run test` + - Executes the tests headless. +1. `npm run ui` + - Executes the tests in a browser handled by a cypress application. + + In case of errors it is recommended to use the UI to debug. diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress.json b/src/Umbraco.Tests.AcceptanceTest/cypress.json new file mode 100644 index 0000000000..33978211ed --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress.json @@ -0,0 +1,11 @@ +{ + "baseUrl": "https://localhost:44331", + "viewportHeight": 1024, + "viewportWidth": 1200, + "env": { + "username": "", + "password": "" + }, + "supportFile": "cypress/support/index.ts", + "videoUploadOnPasses" : false +} diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Login/login.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Login/login.ts new file mode 100644 index 0000000000..4c7720d71f --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Login/login.ts @@ -0,0 +1,62 @@ +/// +context('Login', () => { + + beforeEach(() => { + cy.visit('/umbraco'); + }); + + it('Login with correct username and password', () => { + const username = Cypress.env('username'); + const password = Cypress.env('password'); + //Precondition + cy.get('.text-error').should('not.exist'); + + //Action + cy.get('#umb-username').type(username); + cy.get('#umb-passwordTwo').type(password); + cy.get('[label-key="general_login"]').click(); + + //Assert + cy.url().should('include', '/umbraco#/content') + cy.get('#umb-username').should('not.exist'); + cy.get('#umb-passwordTwo').should('not.exist'); + }); + + + it('Login with correct username but wrong password', () => { + const username = Cypress.env('username'); + const password = 'wrong'; + + //Precondition + cy.get('.text-error').should('not.exist'); + + //Action + cy.get('#umb-username').type(username); + cy.get('#umb-passwordTwo').type(password); + cy.get('[label-key="general_login"]').click(); + + //Assert + cy.get('.text-error').should('exist'); + cy.get('#umb-username').should('exist'); + cy.get('#umb-passwordTwo').should('exist'); + }); + + it('Login with wrong username and wrong password', () => { + const username = 'wrong-username'; + const password = 'wrong'; + + //Precondition + cy.get('.text-error').should('not.exist'); + + //Action + cy.get('#umb-username').type(username); + cy.get('#umb-passwordTwo').type(password); + cy.get('[label-key="general_login"]').click(); + + //Assert + cy.get('.text-error').should('exist'); + cy.get('#umb-username').should('exist'); + cy.get('#umb-passwordTwo').should('exist'); + }); + + }); \ No newline at end of file diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts new file mode 100644 index 0000000000..5803810f54 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts @@ -0,0 +1,66 @@ +/// +import {LabelDataTypeBuilder} from 'umbraco-cypress-testhelpers'; +context('Data Types', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create data type', () => { + const name = "Test data type"; + + cy.umbracoEnsureDataTypeNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Data Types"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.umbracoContextMenuAction("action-data-type").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + + cy.get('select[name="selectedEditor"]').select('Label'); + + cy.get('.umb-property-editor select').select('Time'); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureDataTypeNameNotExists(name); + }); + + it('Delete data type', () => { + const name = "Test data type"; + cy.umbracoEnsureDataTypeNameNotExists(name); + + const dataType = new LabelDataTypeBuilder() + .withSaveNewAction() + .withName(name) + .build(); + + cy.saveDataType(dataType); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Data Types", name]).rightclick(); + + cy.umbracoContextMenuAction("action-delete").click(); + + cy.umbracoButtonByLabelKey("general_delete").click(); + + cy.contains(name).should('not.exist'); + + cy.umbracoEnsureDataTypeNameNotExists(name); + + + }); +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts new file mode 100644 index 0000000000..5a5ba0b3e0 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts @@ -0,0 +1,79 @@ +/// +import { DocumentTypeBuilder } from 'umbraco-cypress-testhelpers'; +context('Document Types', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create document type', () => { + const name = "Test document type"; + + cy.umbracoEnsureDocumentTypeNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Document Types"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.umbracoContextMenuAction("action-documentType").click(); + //Type name + cy.umbracoEditorHeaderName(name); + + + cy.get('[data-element="group-add"]').click(); + + + cy.get('.umb-group-builder__group-title-input').type('Group name'); + cy.get('[data-element="property-add"]').click(); + cy.get('.editor-label').type('property name'); + cy.get('[data-element="editor-add"]').click(); + + //Search for textstring + cy.get('.umb-search-field').type('Textstring'); + + // Choose first item + cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + + // Save property + cy.get('.btn-success').last().click(); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureDocumentTypeNameNotExists(name); + }); + + it('Delete document type', () => { + const name = "Test document type"; + cy.umbracoEnsureDocumentTypeNameNotExists(name); + + const dataType = new DocumentTypeBuilder() + .withName(name) + .build(); + + cy.saveDocumentType(dataType); + + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Document Types", name]).rightclick(); + + cy.umbracoContextMenuAction("action-delete").click(); + + cy.get('label.checkbox').click(); + cy.umbracoButtonByLabelKey("general_ok").click(); + + cy.contains(name).should('not.exist'); + + cy.umbracoEnsureDocumentTypeNameNotExists(name); + + + }); +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts new file mode 100644 index 0000000000..49bcf94943 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/languages.ts @@ -0,0 +1,32 @@ +/// +context('Languages', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Add language', () => { + const name = "Kyrgyz (Kyrgyzstan)"; // Must be an option in the select box + + cy.umbracoEnsureLanguageNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Languages"]).click(); + + cy.umbracoButtonByLabelKey("languages_addLanguage").click(); + + cy.get('select[name="newLang"]').select(name); + + // //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureLanguageNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/macros.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/macros.ts new file mode 100644 index 0000000000..40b5d5483d --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/macros.ts @@ -0,0 +1,33 @@ +/// +context('Macros', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create macro', () => { + const name = "Test macro"; + + cy.umbracoEnsureMacroNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Macros"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + + cy.get('form[name="createMacroForm"]').within(($form) => { + cy.get('input[name="itemKey"]').type(name); + cy.get(".btn-primary").click(); + }); + + cy.location().should((loc) => { + expect(loc.hash).to.include('#/settings/macros/edit/') + }); + + //Clean up + cy.umbracoEnsureMacroNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts new file mode 100644 index 0000000000..a963da754f --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts @@ -0,0 +1,52 @@ +/// +context('Media Types', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create media type', () => { + const name = "Test media type"; + + cy.umbracoEnsureMediaTypeNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Media Types"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); + + + //Type name + cy.umbracoEditorHeaderName(name); + + + cy.get('[data-element="group-add"]').click(); + + cy.get('.umb-group-builder__group-title-input').type('Group name'); + cy.get('[data-element="property-add"]').click(); + cy.get('.editor-label').type('property name'); + cy.get('[data-element="editor-add"]').click(); + + //Search for textstring + cy.get('.umb-search-field').type('Textstring'); + + // Choose first item + cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + + // Save property + cy.get('.btn-success').last().click(); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureMediaTypeNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts new file mode 100644 index 0000000000..53823f2a69 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts @@ -0,0 +1,50 @@ +/// +context('Member Types', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create member type', () => { + const name = "Test member type"; + + cy.umbracoEnsureMemberTypeNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Member Types"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + + cy.get('[data-element="group-add"]').click(); + + cy.get('.umb-group-builder__group-title-input').type('Group name'); + cy.get('[data-element="property-add"]').click(); + cy.get('.editor-label').type('property name'); + cy.get('[data-element="editor-add"]').click(); + + //Search for textstring + cy.get('.umb-search-field').type('Textstring'); + + // Choose first item + cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + + // Save property + cy.get('.btn-success').last().click(); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureMemberTypeNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts new file mode 100644 index 0000000000..f4c976de08 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViewMacroFiles.ts @@ -0,0 +1,37 @@ +/// +context('Partial View Macro Files', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create new partial view macro', () => { + const name = "TestPartialViewMacro"; + const fileName = name + ".cshtml"; + + cy.umbracoEnsurePartialViewMacroFileNameNotExists(fileName); + cy.umbracoEnsureMacroNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Partial View Macro Files"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-label").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsurePartialViewMacroFileNameNotExists(fileName); + cy.umbracoEnsureMacroNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts new file mode 100644 index 0000000000..b644c6642b --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/partialsViews.ts @@ -0,0 +1,35 @@ +/// +context('Partial Views', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create new empty partial view', () => { + const name = "TestPartialView"; + const fileName = name + ".cshtml"; + + cy.umbracoEnsurePartialViewNameNotExists(fileName); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Partial Views"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsurePartialViewNameNotExists(fileName); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts new file mode 100644 index 0000000000..4773f37c37 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/relationTypes.ts @@ -0,0 +1,40 @@ +/// +context('Relation Types', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create relation type', () => { + const name = "Test relation type"; + + cy.umbracoEnsureRelationTypeNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Relation Types"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + + cy.get('form[name="createRelationTypeForm"]').within(($form) => { + cy.get('input[name="relationTypeName"]').type(name); + + cy.get('[name="relationType-direction"] input').first().click({force:true}); + + cy.get('select[name="relationType-parent"]').select('Document'); + + cy.get('select[name="relationType-child"]').select('Media'); + + cy.get(".btn-primary").click(); + }); + + cy.location().should((loc) => { + expect(loc.hash).to.include('#/settings/relationTypes/edit/') + }) + + //Clean up + cy.umbracoEnsureRelationTypeNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts new file mode 100644 index 0000000000..8cffd3e59b --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/scripts.ts @@ -0,0 +1,35 @@ +/// +context('Scripts', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create new JavaScript file', () => { + const name = "TestScript"; + const fileName = name + ".js"; + + cy.umbracoEnsureScriptNameNotExists(fileName); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Stylesheets"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureScriptNameNotExists(fileName); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/stylesheets.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/stylesheets.ts new file mode 100644 index 0000000000..2a1286c499 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/stylesheets.ts @@ -0,0 +1,35 @@ +/// +context('Stylesheets', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create new style sheet file', () => { + const name = "TestStylesheet"; + const fileName = name + ".css"; + + cy.umbracoEnsureStylesheetNameNotExists(fileName); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Stylesheets"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); // TODO: Fucked we cant use something like cy.umbracoContextMenuAction("action-mediaType").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + //Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureStylesheetNameNotExists(fileName); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts new file mode 100644 index 0000000000..6871db7ffe --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -0,0 +1,57 @@ +/// +import {DocumentTypeBuilder, TemplateBuilder} from "umbraco-cypress-testhelpers"; + +context('Templates', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create template', () => { + const name = "Test template"; + + cy.umbracoEnsureTemplateNameNotExists(name); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Templates"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + //Save + cy.get("form[name='contentForm']").submit(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureTemplateNameNotExists(name); + }); + + it('Delete template', () => { + const name = "Test template"; + cy.umbracoEnsureTemplateNameNotExists(name); + + const template = new TemplateBuilder() + .withName(name) + .build(); + + cy.saveTemplate(template); + + cy.umbracoSection('settings'); + cy.get('li .umb-tree-root:contains("Settings")').should("be.visible"); + + cy.umbracoTreeItem("settings", ["Templates", name]).rightclick(); + cy.umbracoContextMenuAction("action-delete").click(); + + cy.umbracoButtonByLabelKey("general_ok").click(); + + cy.contains(name).should('not.exist'); + + cy.umbracoEnsureTemplateNameNotExists(name); + }); +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts new file mode 100644 index 0000000000..905d0fc25c --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts @@ -0,0 +1,36 @@ + +context('User Groups', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create user group', () => { + const name = "Test Group"; + + cy.umbracoEnsureUserGroupNameNotExists(name); + + cy.umbracoSection('users'); + cy.get('[data-element="sub-view-userGroups"]').click(); + + cy.umbracoButtonByLabelKey("actions_createGroup").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + // Assign sections + cy.get('.umb-box:nth-child(1) .umb-property:nth-child(1) localize').click(); + cy.get('.umb-tree-item span').click({multiple:true}); + cy.get('.btn-success').last().click(); + + // Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureUserGroupNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/users.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/users.ts new file mode 100644 index 0000000000..e122b21564 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/users.ts @@ -0,0 +1,35 @@ +/// +context('Users', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create user', () => { + const name = "Alice Bobson"; + const email = "alice-bobson@acceptancetest.umbraco"; + + cy.umbracoEnsureUserEmailNotExists(email); + cy.umbracoSection('users'); + cy.umbracoButtonByLabelKey("user_createUser").click(); + + + cy.get('input[name="name"]').type(name); + cy.get('input[name="email"]').type(email); + + cy.get('.umb-node-preview-add').click(); + cy.get('.umb-user-group-picker-list-item:nth-child(1) > .umb-user-group-picker__action').click(); + cy.get('.umb-user-group-picker-list-item:nth-child(2) > .umb-user-group-picker__action').click(); + cy.get('.btn-success').click(); + + cy.get('.umb-button > .btn > .umb-button__content').click(); + + + cy.umbracoButtonByLabelKey("user_goToProfile").should('be.visible'); + + //Clean up + cy.umbracoEnsureUserEmailNotExists(email); + + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js b/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js new file mode 100644 index 0000000000..aa9918d215 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/plugins/index.js @@ -0,0 +1,21 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js b/src/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js new file mode 100644 index 0000000000..5b0be47114 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/support/commands.js @@ -0,0 +1,30 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) + +import {Command} from 'umbraco-cypress-testhelpers'; +import {Chainable} from './chainable'; +new Chainable(); +new Command().registerCypressCommands(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/support/index.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/support/index.ts new file mode 100644 index 0000000000..d68db96df2 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/support/index.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/tsconfig.json b/src/Umbraco.Tests.AcceptanceTest/cypress/tsconfig.json new file mode 100644 index 0000000000..70cf2f6751 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "include": [ + "../node_modules/cypress", + "*/*.ts" + ] +} diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/typings.d.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/typings.d.ts new file mode 100644 index 0000000000..3b7cf0b3e9 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/typings.d.ts @@ -0,0 +1,5 @@ +// type definitions for Cypress object "cy" +/// + +// type definitions for custom commands like "createDefaultTodos" +// diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json new file mode 100644 index 0000000000..ad125d090a --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -0,0 +1,15 @@ +{ + "scripts": { + "test": "npx cypress run", + "ui": "npx cypress open" + }, + "devDependencies": { + "cross-env": "^7.0.2", + "ncp": "^2.0.0", + "cypress": "^4.6.0", + "umbraco-cypress-testhelpers": "1.0.0-beta-39" + }, + "dependencies": { + "typescript": "^3.9.2" + } +} diff --git a/src/Umbraco.Tests.AcceptanceTest/tsconfig.json b/src/Umbraco.Tests.AcceptanceTest/tsconfig.json new file mode 100644 index 0000000000..6cb05bfcc7 --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./lib", + "sourceMap": false, + "declaration": true, + "module": "CommonJS", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "importHelpers": true, + "target": "es5", + + "types": [ + "cypress" + ], + "lib": [ + "es5", + "dom" + ], + "plugins": [ + { + "name": "typescript-tslint-plugin", + "alwaysShowRuleFailuresAsWarnings": false, + "ignoreDefinitionFiles": true, + "configFile": "tslint.json", + "suppressWhileTypeErrorsPresent": false + } + ] + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/src/Umbraco.Tests.AcceptanceTest/tslint.json b/src/Umbraco.Tests.AcceptanceTest/tslint.json new file mode 100644 index 0000000000..a8571f3cda --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["tslint:recommended", "tslint-config-prettier"] +} diff --git a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs index 344d7bcf87..bd85807203 100644 --- a/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/SmtpSettingsBuilder.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Configuration; +using System.Net.Mail; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.Common.Builders { @@ -16,6 +18,9 @@ namespace Umbraco.Tests.Common.Builders private string _host; private int? _port; private string _pickupDirectoryLocation; + private SmtpDeliveryMethod? _deliveryMethod; + private string _username; + private string _password; public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -33,24 +38,45 @@ namespace Umbraco.Tests.Common.Builders return this; } + public SmtpSettingsBuilder WithUsername(string username) + { + _username = username; + return this; + } + public SmtpSettingsBuilder WithPost(int port) { _port = port; return this; } + public SmtpSettingsBuilder WithPassword(string password) + { + _password = password; + return this; + } + public SmtpSettingsBuilder WithPickupDirectoryLocation(string pickupDirectoryLocation) { _pickupDirectoryLocation = pickupDirectoryLocation; return this; } + public SmtpSettingsBuilder WithDeliveryMethod(SmtpDeliveryMethod deliveryMethod) + { + _deliveryMethod = deliveryMethod; + return this; + } + public override ISmtpSettings Build() { var from = _from ?? null; var host = _host ?? null; var port = _port ?? 25; var pickupDirectoryLocation = _pickupDirectoryLocation ?? null; + var deliveryMethod = _deliveryMethod ?? SmtpDeliveryMethod.Network; + var username = _username ?? null; + var password = _password ?? null; return new TestSmtpSettings() { @@ -58,6 +84,9 @@ namespace Umbraco.Tests.Common.Builders Host = host, Port = port, PickupDirectoryLocation = pickupDirectoryLocation, + DeliveryMethod = deliveryMethod, + Username = username, + Password = password, }; } @@ -67,6 +96,9 @@ namespace Umbraco.Tests.Common.Builders public string Host { get; set; } public int Port { get; set; } public string PickupDirectoryLocation { get; set; } + public SmtpDeliveryMethod DeliveryMethod { get; set; } + public string Username { get; set; } + public string Password { get; set; } } } } diff --git a/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs b/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs new file mode 100644 index 0000000000..d5250c3739 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/XmlDocumentBuilder.cs @@ -0,0 +1,125 @@ +using System.Xml; + +namespace Umbraco.Tests.Common.Builders +{ + public class XmlDocumentBuilder : BuilderBase + { + private string _content; + + public XmlDocumentBuilder WithContent(string content) + { + _content = content; + return this; + } + + public override XmlDocument Build() + { + var xml = new XmlDocument(); + xml.LoadXml(_content ?? DefaultContent); + return xml; + } + + private const string DefaultContent = + @" + + + + + ]> + + + + + 1 + + This is some content]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + } +} diff --git a/src/Umbraco.Tests/Misc/DateTimeExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/DateTimeExtensionsTests.cs similarity index 97% rename from src/Umbraco.Tests/Misc/DateTimeExtensionsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/DateTimeExtensionsTests.cs index 99d3943a04..b1585aa17f 100644 --- a/src/Umbraco.Tests/Misc/DateTimeExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/DateTimeExtensionsTests.cs @@ -2,7 +2,7 @@ using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.Misc +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class DateTimeExtensionsTests diff --git a/src/Umbraco.Tests/Misc/HashCodeCombinerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs similarity index 97% rename from src/Umbraco.Tests/Misc/HashCodeCombinerTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs index 6a30e09d84..ecd2d13e78 100644 --- a/src/Umbraco.Tests/Misc/HashCodeCombinerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs @@ -4,20 +4,21 @@ using System.Reflection; using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.Misc +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class HashCodeCombinerTests { - private DirectoryInfo PrepareFolder() { var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N"))); + var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", + Guid.NewGuid().ToString("N"))); foreach (var f in dir.GetFiles()) { f.Delete(); } + return dir; } @@ -80,6 +81,7 @@ namespace Umbraco.Tests.Misc { file1.WriteLine("hello"); } + var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); File.Delete(file2Path); using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) @@ -140,6 +142,5 @@ namespace Umbraco.Tests.Misc Assert.AreNotEqual(combiner1.GetCombinedHashCode(), combiner3.GetCombinedHashCode()); } - } } diff --git a/src/Umbraco.Tests/Misc/HashGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs similarity index 98% rename from src/Umbraco.Tests/Misc/HashGeneratorTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs index 1b79494164..08f5b69cab 100644 --- a/src/Umbraco.Tests/Misc/HashGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs @@ -4,7 +4,7 @@ using System.Reflection; using NUnit.Framework; using Umbraco.Core; -namespace Umbraco.Tests.Misc +namespace Umbraco.Tests.UnitTests.Umbraco.Core { [TestFixture] public class HashGeneratorTests @@ -20,6 +20,7 @@ namespace Umbraco.Tests.Misc else generator.AddCaseInsensitiveString(str); } + return generator.GenerateHash(); } } @@ -27,7 +28,6 @@ namespace Umbraco.Tests.Misc [Test] public void Generate_Hash_Multiple_Strings_Case_Sensitive() { - var hash1 = Generate(true, "hello", "world"); var hash2 = Generate(true, "hello", "world"); var hashFalse1 = Generate(true, "hello", "worlD"); @@ -54,11 +54,13 @@ namespace Umbraco.Tests.Misc private DirectoryInfo PrepareFolder() { var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N"))); + var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", + Guid.NewGuid().ToString("N"))); foreach (var f in dir.GetFiles()) { f.Delete(); } + return dir; } @@ -74,7 +76,6 @@ namespace Umbraco.Tests.Misc combiner2.AddCaseInsensitiveString("world"); Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); } - } [Test] @@ -120,6 +121,7 @@ namespace Umbraco.Tests.Misc { file1.WriteLine("hello"); } + var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); File.Delete(file2Path); using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) diff --git a/src/Umbraco.Tests/Misc/UriUtilityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs similarity index 71% rename from src/Umbraco.Tests/Misc/UriUtilityTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs index a10d6e2179..15a9599674 100644 --- a/src/Umbraco.Tests/Misc/UriUtilityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs @@ -1,25 +1,18 @@ using System; -using System.Configuration; using Moq; using NUnit.Framework; -using Umbraco.Tests.TestHelpers; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Hosting; using Umbraco.Web; -namespace Umbraco.Tests.Misc +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing { // FIXME: not testing virtual directory! [TestFixture] public class UriUtilityTests { - - public UriUtility UriUtility { get; } = TestHelper.UriUtility; - [TearDown] - public void TearDown() - { - SettingsForTests.Reset(); - } - // test normal urls [TestCase("http://LocalHost/", "http://localhost/")] [TestCase("http://LocalHost/?x=y", "http://localhost/?x=y")] @@ -50,19 +43,20 @@ namespace Umbraco.Tests.Misc [TestCase("http://Localhost/Home/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] [TestCase("http://Localhost/Home.aspx/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] [TestCase("http://Localhost/deFault.aspx/Home.aspx/deFault.aspx/Sub1.aspx", "http://localhost/home/default/sub1")] - public void Uri_To_Umbraco(string sourceUrl, string expectedUrl) { - UriUtility.SetAppDomainAppVirtualPath("/"); - - var expectedUri = new Uri(expectedUrl); + // Arrange var sourceUri = new Uri(sourceUrl); - var resultUri = UriUtility.UriToUmbraco(sourceUri); + var uriUtility = BuildUriUtility("/"); + // Act + var resultUri = uriUtility.UriToUmbraco(sourceUri); + + // Assert + var expectedUri = new Uri(expectedUrl); Assert.AreEqual(expectedUri.ToString(), resultUri.ToString()); } - // test directoryUrl true, trailingSlash false [TestCase("/", "/", false)] [TestCase("/home", "/home", false)] @@ -72,30 +66,22 @@ namespace Umbraco.Tests.Misc [TestCase("/", "/", true)] [TestCase("/home", "/home/", true)] [TestCase("/home/sub1", "/home/sub1/", true)] - public void Uri_From_Umbraco(string sourceUrl, string expectedUrl, bool trailingSlash) { - var globalConfig = Mock.Get(SettingsForTests.GenerateMockGlobalSettings()); + // Arrange + var sourceUri = new Uri(sourceUrl, UriKind.Relative); + var mockRequestHandlerSettings = new Mock(); + mockRequestHandlerSettings.Setup(x => x.AddTrailingSlash).Returns(trailingSlash); + var uriUtility = BuildUriUtility("/"); - var settings = SettingsForTests.GenerateMockRequestHandlerSettings(); - var requestMock = Mock.Get(settings); - requestMock.Setup(x => x.AddTrailingSlash).Returns(trailingSlash); - - UriUtility.SetAppDomainAppVirtualPath("/"); - - var expectedUri = NewUri(expectedUrl); - var sourceUri = NewUri(sourceUrl); - var resultUri = UriUtility.UriFromUmbraco(sourceUri, globalConfig.Object, settings); + // Act + var resultUri = uriUtility.UriFromUmbraco(sourceUri, Mock.Of(), mockRequestHandlerSettings.Object); + // Assert + var expectedUri = new Uri(expectedUrl, UriKind.Relative); Assert.AreEqual(expectedUri.ToString(), resultUri.ToString()); } - Uri NewUri(string url) - { - return new Uri(url, url.StartsWith("http:") ? UriKind.Absolute : UriKind.Relative); - } - - // [TestCase("/", "/", "/")] [TestCase("/", "/foo", "/foo")] [TestCase("/", "~/foo", "/foo")] @@ -103,15 +89,18 @@ namespace Umbraco.Tests.Misc [TestCase("/vdir", "/foo", "/vdir/foo")] [TestCase("/vdir", "/foo/", "/vdir/foo/")] [TestCase("/vdir", "~/foo", "/vdir/foo")] - public void Uri_To_Absolute(string virtualPath, string sourceUrl, string expectedUrl) { - UriUtility.SetAppDomainAppVirtualPath(virtualPath); - var resultUrl = UriUtility.ToAbsolute(sourceUrl); + // Arrange + var uriUtility = BuildUriUtility(virtualPath); + + // Act + var resultUrl = uriUtility.ToAbsolute(sourceUrl); + + // Assert Assert.AreEqual(expectedUrl, resultUrl); } - // [TestCase("/", "/", "/")] [TestCase("/", "/foo", "/foo")] [TestCase("/", "/foo/", "/foo/")] @@ -119,12 +108,23 @@ namespace Umbraco.Tests.Misc [TestCase("/vdir", "/vdir/", "/")] [TestCase("/vdir", "/vdir/foo", "/foo")] [TestCase("/vdir", "/vdir/foo/", "/foo/")] - public void Url_To_App_Relative(string virtualPath, string sourceUrl, string expectedUrl) { - UriUtility.SetAppDomainAppVirtualPath(virtualPath); - var resultUrl = UriUtility.ToAppRelative(sourceUrl); + // Arrange + var uriUtility = BuildUriUtility(virtualPath); + + // Act + var resultUrl = uriUtility.ToAppRelative(sourceUrl); + + // Assert Assert.AreEqual(expectedUrl, resultUrl); } + + private UriUtility BuildUriUtility(string virtualPath) + { + var mockHostingEnvironment = new Mock(); + mockHostingEnvironment.Setup(x => x.ApplicationVirtualPath).Returns(virtualPath); + return new UriUtility(mockHostingEnvironment.Object); + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs new file mode 100644 index 0000000000..495ca0186d --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs @@ -0,0 +1,157 @@ +using System.Diagnostics; +using System.Linq; +using System.Xml; +using System.Xml.XPath; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Xml; +using Umbraco.Tests.Common.Builders; + +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml +{ + [TestFixture] + public class XmlHelperTests + { + private XmlDocumentBuilder _builder; + + [SetUp] + public void SetUp() + { + _builder = new XmlDocumentBuilder(); + } + + [Ignore("This is a benchmark test so is ignored by default")] + [Test] + public void Sort_Nodes_Benchmark_Legacy() + { + var xml = _builder.Build(); + var original = xml.GetElementById(1173.ToString()); + Assert.IsNotNull(original); + + long totalTime = 0; + var watch = new Stopwatch(); + var iterations = 10000; + + for (var i = 0; i < iterations; i++) + { + //don't measure the time for clone! + var parentNode = original.Clone(); + watch.Start(); + LegacySortNodes(ref parentNode); + watch.Stop(); + totalTime += watch.ElapsedMilliseconds; + watch.Reset(); + + //do assertions just to make sure it is working properly. + var currSort = 0; + foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) + { + Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); + currSort++; + } + + //ensure the parent node's properties still exist first + Assert.AreEqual("content", parentNode.ChildNodes[0].Name); + Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); + //then the child nodes should come straight after + Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); + } + + Debug.WriteLine("Total time for " + iterations + " iterations is " + totalTime); + } + + [Ignore("This is a benchmark test so is ignored by default")] + [Test] + public void Sort_Nodes_Benchmark_New() + { + var xml = _builder.Build(); + var original = xml.GetElementById(1173.ToString()); + Assert.IsNotNull(original); + + long totalTime = 0; + var watch = new Stopwatch(); + var iterations = 10000; + + for (var i = 0; i < iterations; i++) + { + //don't measure the time for clone! + var parentNode = (XmlElement) original.Clone(); + watch.Start(); + XmlHelper.SortNodes( + parentNode, + "./* [@id]", + x => x.AttributeValue("sortOrder")); + watch.Stop(); + totalTime += watch.ElapsedMilliseconds; + watch.Reset(); + + //do assertions just to make sure it is working properly. + var currSort = 0; + foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) + { + Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); + currSort++; + } + + //ensure the parent node's properties still exist first + Assert.AreEqual("content", parentNode.ChildNodes[0].Name); + Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); + //then the child nodes should come straight after + Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); + } + + Debug.WriteLine("Total time for " + iterations + " iterations is " + totalTime); + } + + [Test] + public void Sort_Nodes() + { + var xml = _builder.Build(); + var original = xml.GetElementById(1173.ToString()); + Assert.IsNotNull(original); + + var parentNode = (XmlElement) original.Clone(); + + XmlHelper.SortNodes( + parentNode, + "./* [@id]", + x => x.AttributeValue("sortOrder")); + + //do assertions just to make sure it is working properly. + var currSort = 0; + foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) + { + Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); + currSort++; + } + + //ensure the parent node's properties still exist first + Assert.AreEqual("content", parentNode.ChildNodes[0].Name); + Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); + //then the child nodes should come straight after + Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); + } + + /// + /// This was the logic to sort before and now lives here just to show the benchmarks tests above. + /// + /// + private static void LegacySortNodes(ref XmlNode parentNode) + { + var n = parentNode.CloneNode(true); + + // remove all children from original node + var xpath = "./* [@id]"; + foreach (XmlNode child in parentNode.SelectNodes(xpath)) + parentNode.RemoveChild(child); + + var nav = n.CreateNavigator(); + var expr = nav.Compile(xpath); + expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); + var iterator = nav.Select(expr); + while (iterator.MoveNext()) + parentNode.AppendChild( + ((IHasXmlNode) iterator.Current).GetNode()); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs new file mode 100644 index 0000000000..64f305edd8 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class XmlDocumentBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const string content = + @""; + + var builder = new XmlDocumentBuilder(); + + // Act + var xml = builder + .WithContent(content) + .Build(); + + // Assert + Assert.AreEqual(content, xml.OuterXml); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index f60b400f3f..67d822fb03 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -20,6 +20,8 @@ + + diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs new file mode 100644 index 0000000000..c91660563e --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models.Membership; +using Umbraco.Web; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters +{ + [TestFixture] + public class AppendUserModifiedHeaderAttributeTests + { + [Test] + public void Appends_Header_When_No_User_Parameter_Provider() + { + // Arrange + var context = CreateContext(); + var attribute = new AppendUserModifiedHeaderAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue); + Assert.AreEqual("1", headerValue[0]); + } + + [Test] + public void Does_Not_Append_Header_If_Already_Exists() + { + // Arrange + var context = CreateContext(headerValue: "0"); + var attribute = new AppendUserModifiedHeaderAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue); + Assert.AreEqual("0", headerValue[0]); + } + + [Test] + public void Does_Not_Append_Header_When_User_Id_Parameter_Provided_And_Does_Not_Match_Current_User() + { + // Arrange + var context = CreateContext(actionArgument: new KeyValuePair("UserId", 99)); + var userIdParameter = "UserId"; + var attribute = new AppendUserModifiedHeaderAttribute(userIdParameter); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsFalse(context.HttpContext.Response.Headers.ContainsKey("X-Umb-User-Modified")); + } + + [Test] + public void Appends_Header_When_User_Id_Parameter_Provided_And_Does_Not_Match_Current_User() + { + // Arrange + var context = CreateContext(actionArgument: new KeyValuePair("UserId", 100)); + var userIdParameter = "UserId"; + var attribute = new AppendUserModifiedHeaderAttribute(userIdParameter); + + // Act + attribute.OnActionExecuting(context); + + // Assert + context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue); + Assert.AreEqual("1", headerValue[0]); + } + + private static ActionExecutingContext CreateContext(string headerValue = null, KeyValuePair actionArgument = default) + { + var httpContext = new DefaultHttpContext(); + if (!string.IsNullOrEmpty(headerValue)) + { + httpContext.Response.Headers.Add("X-Umb-User-Modified", headerValue); + } + + var currentUserMock = new Mock(); + currentUserMock + .SetupGet(x => x.Id) + .Returns(100); + + var webSecurityMock = new Mock(); + webSecurityMock + .SetupGet(x => x.CurrentUser) + .Returns(currentUserMock.Object); + + var umbracoContextMock = new Mock(); + umbracoContextMock + .SetupGet(x => x.Security) + .Returns(webSecurityMock.Object); + + var umbracoContextAccessorMock = new Mock(); + umbracoContextAccessorMock + .SetupGet(x => x.UmbracoContext) + .Returns(umbracoContextMock.Object); + + var serviceProviderMock = new Mock(); + serviceProviderMock + .Setup(x => x.GetService(typeof(IUmbracoContextAccessor))) + .Returns(umbracoContextAccessorMock.Object); + + httpContext.RequestServices = serviceProviderMock.Object; + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + var context = new ActionExecutingContext( + actionContext, + new List(), + new Dictionary(), + new Mock().Object); + + if (!EqualityComparer>.Default.Equals(actionArgument, default)) + { + context.ActionArguments.Add(actionArgument); + } + + return context; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs new file mode 100644 index 0000000000..7d1fbdddf1 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Web.BackOffice.Filters; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters +{ + [TestFixture] + public class OnlyLocalRequestsAttributeTests + { + [Test] + public void Does_Not_Set_Result_When_No_Remote_Address() + { + // Arrange + var context = CreateContext(); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Does_Not_Set_Result_When_Remote_Address_Is_Null_Ip_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "::1"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Does_Not_Set_Result_When_Remote_Address_Matches_Local_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.3"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Returns_Not_Found_When_Remote_Address_Does_Not_Match_Local_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.2"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + var typedResult = context.Result as NotFoundResult; + Assert.IsNotNull(typedResult); + } + + [Test] + public void Does_Not_Set_Result_When_Remote_Address_Matches_LoopBack_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "127.0.0.1", localIpAddress: "::1"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Returns_Not_Found_When_Remote_Address_Does_Not_Match_LoopBack_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "::1"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + var typedResult = context.Result as NotFoundResult; + Assert.IsNotNull(typedResult); + } + + private static ActionExecutingContext CreateContext(string remoteIpAddress = null, string localIpAddress = null) + { + var httpContext = new DefaultHttpContext(); + if (!string.IsNullOrEmpty(remoteIpAddress)) + { + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(remoteIpAddress); + } + + if (!string.IsNullOrEmpty(localIpAddress)) + { + httpContext.Connection.LocalIpAddress = IPAddress.Parse(localIpAddress); + } + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + return new ActionExecutingContext( + actionContext, + new List(), + new Dictionary(), + new Mock().Object); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs new file mode 100644 index 0000000000..51521c48fa --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Web.BackOffice.Filters; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters +{ + [TestFixture] + public class ValidationFilterAttributeTests + { + [Test] + public void Does_Not_Set_Result_When_No_Errors_In_Model_State() + { + // Arrange + var context = CreateContext(); + var attribute = new ValidationFilterAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Returns_Bad_Request_When_Errors_In_Model_State() + { + // Arrange + var context = CreateContext(withError: true); + var attribute = new ValidationFilterAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + var typedResult = context.Result as BadRequestObjectResult; + Assert.IsNotNull(typedResult); + } + + private static ActionExecutingContext CreateContext(bool withError = false) + { + var httpContext = new DefaultHttpContext(); + + var modelState = new ModelStateDictionary(); + if (withError) + { + modelState.AddModelError(string.Empty, "Error"); + } + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor(), modelState); + + return new ActionExecutingContext( + actionContext, + new List(), + new Dictionary(), + new Mock().Object); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs new file mode 100644 index 0000000000..0ad8e2c421 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs @@ -0,0 +1,128 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Models; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders +{ + [TestFixture] + public class ContentModelBinderTests + { + [Test] + public void Does_Not_Bind_Model_When_UmbracoDataToken_Not_In_Route_Data() + { + // Arrange + var bindingContext = CreateBindingContext(typeof(ContentModel), withUmbracoDataToken: false); + var binder = new ContentModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.False(bindingContext.Result.IsModelSet); + } + + [Test] + public void Does_Not_Bind_Model_When_Source_Not_Of_Expected_Type() + { + // Arrange + var bindingContext = CreateBindingContext(typeof(ContentModel), source: new NonContentModel()); + var binder = new ContentModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.False(bindingContext.Result.IsModelSet); + } + + [Test] + public void Does_Not_Bind_Model_When_Source_Type_Matches_Model_Type() + { + // Arrange + var bindingContext = CreateBindingContext(typeof(ContentModel), source: new ContentModel(CreatePublishedContent())); + var binder = new ContentModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.False(bindingContext.Result.IsModelSet); + } + + [Test] + public void Binds_From_IPublishedContent_To_Content_Model() + { + // Arrange + var bindingContext = CreateBindingContext(typeof(ContentModel), source: CreatePublishedContent()); + var binder = new ContentModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + } + + [Test] + public void Binds_From_IPublishedContent_To_Content_Model_Of_T() + { + // Arrange + var bindingContext = CreateBindingContext(typeof(ContentModel), source: new ContentModel(new ContentType2(CreatePublishedContent()))); + var binder = new ContentModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + } + + private ModelBindingContext CreateBindingContext(Type modelType, bool withUmbracoDataToken = true, object source = null) + { + var httpContext = new DefaultHttpContext(); + var routeData = new RouteData(); + if (withUmbracoDataToken) + routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + + var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); + var metadataProvider = new EmptyModelMetadataProvider(); + var routeValueDictionary = new RouteValueDictionary(); + var valueProvider = new RouteValueProvider(BindingSource.Path, routeValueDictionary); + return new DefaultModelBindingContext + { + ActionContext = actionContext, + ModelMetadata = metadataProvider.GetMetadataForType(modelType), + ModelName = modelType.Name, + ValueProvider = valueProvider, + }; + } + + private class NonContentModel + { + } + + private IPublishedContent CreatePublishedContent() + { + return new ContentType2(new Mock().Object); + } + + public class ContentType1 : PublishedContentWrapped + { + public ContentType1(IPublishedContent content) : base(content) { } + } + + public class ContentType2 : ContentType1 + { + public ContentType2(IPublishedContent content) : base(content) { } + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs new file mode 100644 index 0000000000..6dd3b024b3 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs @@ -0,0 +1,90 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Primitives; +using NUnit.Framework; +using Umbraco.Web.Common.ModelBinders; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders +{ + [TestFixture] + public class HttpQueryStringModelBinderTests + { + [Test] + public void Binds_Query_To_FormCollection() + { + // Arrange + var bindingContext = CreateBindingContext("?foo=bar&baz=buzz"); + var binder = new HttpQueryStringModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var typedModel = bindingContext.Result.Model as FormCollection; + Assert.IsNotNull(typedModel); + Assert.AreEqual(typedModel["foo"], "bar"); + Assert.AreEqual(typedModel["baz"], "buzz"); + } + + [Test] + public void Sets_Culture_Form_Value_From_Query_If_Provided() + { + // Arrange + var bindingContext = CreateBindingContext("?foo=bar&baz=buzz&culture=en-gb"); + var binder = new HttpQueryStringModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var typedModel = bindingContext.Result.Model as FormCollection; + Assert.IsNotNull(typedModel); + Assert.AreEqual(typedModel["culture"], "en-gb"); + } + + [Test] + public void Sets_Culture_Form_Value_From_Header_If_Not_Provided_In_Query() + { + // Arrange + var bindingContext = CreateBindingContext("?foo=bar&baz=buzz"); + var binder = new HttpQueryStringModelBinder(); + + // Act + binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var typedModel = bindingContext.Result.Model as FormCollection; + Assert.IsNotNull(typedModel); + Assert.AreEqual(typedModel["culture"], "en-gb"); + } + + private ModelBindingContext CreateBindingContext(string querystring) + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new QueryString(querystring); + httpContext.Request.Headers.Add("X-UMB-CULTURE", new StringValues("en-gb")); + var routeData = new RouteData(); + var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); + var metadataProvider = new EmptyModelMetadataProvider(); + var routeValueDictionary = new RouteValueDictionary(); + var valueProvider = new RouteValueProvider(BindingSource.Path, routeValueDictionary); + var modelType = typeof(FormCollection); + return new DefaultModelBindingContext + { + ActionContext = actionContext, + ModelMetadata = metadataProvider.GetMetadataForType(modelType), + ModelName = modelType.Name, + ValueProvider = valueProvider, + }; + } + } +} diff --git a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs index 4ec6dfc0d5..6819823dd5 100644 --- a/src/Umbraco.Tests/Composing/ContainerConformingTests.cs +++ b/src/Umbraco.Tests/Composing/ContainerConformingTests.cs @@ -333,6 +333,24 @@ namespace Umbraco.Tests.Composing Assert.AreSame(s1, s2); } + [Test] + public void CanRegisterMultipleSameTypeParametersWithCreateInstance() + { + var register = GetRegister(); + + register.Register(c => + { + const string param1 = "param1"; + const string param2 = "param2"; + + return c.CreateInstance(param1, param2); + }); + + var factory = register.CreateFactory(); + var instance = factory.GetInstance(); + Assert.AreNotEqual(instance.Thing, instance.AnotherThing); + } + public interface IThing { } public abstract class ThingBase : IThing { } @@ -353,5 +371,17 @@ namespace Umbraco.Tests.Composing public IEnumerable Things { get; } } + + public class Thing4 : ThingBase + { + public readonly string Thing; + public readonly string AnotherThing; + + public Thing4(string thing, string anotherThing) + { + Thing = thing; + AnotherThing = anotherThing; + } + } } } diff --git a/src/Umbraco.Tests/CoreThings/ClaimsIdentityExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/ClaimsIdentityExtensionsTests.cs new file mode 100644 index 0000000000..4f728c3861 --- /dev/null +++ b/src/Umbraco.Tests/CoreThings/ClaimsIdentityExtensionsTests.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Web; + +namespace Umbraco.Tests.CoreThings +{ + public class ClaimsIdentityExtensionsTests + { + [Test] + public void FindFirstValue_WhenIdentityIsNull_ExpectArgumentNullException() + { + ClaimsIdentity identity = null; + Assert.Throws(() => identity.FindFirstValue("test")); + } + + [Test] + public void FindFirstValue_WhenClaimNotPresent_ExpectNull() + { + var identity = new ClaimsIdentity(new List()); + var value = identity.FindFirstValue("test"); + Assert.IsNull(value); + } + + [Test] + public void FindFirstValue_WhenMatchingClaimPresent_ExpectCorrectValue() + { + var expectedClaim = new Claim("test", "123", "string", "Umbraco"); + var identity = new ClaimsIdentity(new List {expectedClaim}); + + var value = identity.FindFirstValue("test"); + + Assert.AreEqual(expectedClaim.Value, value); + } + + [Test] + public void FindFirstValue_WhenMultipleMatchingClaimsPresent_ExpectFirstValue() + { + var expectedClaim = new Claim("test", "123", "string", "Umbraco"); + var dupeClaim = new Claim(expectedClaim.Type, Guid.NewGuid().ToString()); + var identity = new ClaimsIdentity(new List {expectedClaim, dupeClaim}); + + var value = identity.FindFirstValue("test"); + + Assert.AreEqual(expectedClaim.Value, value); + } + } +} diff --git a/src/Umbraco.Tests/Misc/XmlHelperTests.cs b/src/Umbraco.Tests/Misc/XmlHelperTests.cs deleted file mode 100644 index cb666a72fb..0000000000 --- a/src/Umbraco.Tests/Misc/XmlHelperTests.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System.Diagnostics; -using System.Linq; -using System.Xml; -using System.Xml.XPath; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Xml; - -namespace Umbraco.Tests.Misc -{ - [TestFixture] - public class XmlHelperTests - { - [SetUp] - public void Setup() - { - } - - [Ignore("This is a benchmark test so is ignored by default")] - [Test] - public void Sort_Nodes_Benchmark_Legacy() - { - var xmlContent = GetXmlContent(1); - var xml = new XmlDocument(); - xml.LoadXml(xmlContent); - var original = xml.GetElementById(1173.ToString()); - Assert.IsNotNull(original); - - long totalTime = 0; - var watch = new Stopwatch(); - var iterations = 10000; - - for (var i = 0; i < iterations; i++) - { - //don't measure the time for clone! - var parentNode = original.Clone(); - watch.Start(); - LegacySortNodes(ref parentNode); - watch.Stop(); - totalTime += watch.ElapsedMilliseconds; - watch.Reset(); - - //do assertions just to make sure it is working properly. - var currSort = 0; - foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) - { - Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); - currSort++; - } - - //ensure the parent node's properties still exist first - Assert.AreEqual("content", parentNode.ChildNodes[0].Name); - Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); - //then the child nodes should come straight after - Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); - } - - Debug.WriteLine("Total time for " + iterations + " iterations is " + totalTime); - } - - [Ignore("This is a benchmark test so is ignored by default")] - [Test] - public void Sort_Nodes_Benchmark_New() - { - var xmlContent = GetXmlContent(1); - var xml = new XmlDocument(); - xml.LoadXml(xmlContent); - var original = xml.GetElementById(1173.ToString()); - Assert.IsNotNull(original); - - long totalTime = 0; - var watch = new Stopwatch(); - var iterations = 10000; - - for (var i = 0; i < iterations; i++) - { - //don't measure the time for clone! - var parentNode = (XmlElement)original.Clone(); - watch.Start(); - XmlHelper.SortNodes( - parentNode, - "./* [@id]", - x => x.AttributeValue("sortOrder")); - watch.Stop(); - totalTime += watch.ElapsedMilliseconds; - watch.Reset(); - - //do assertions just to make sure it is working properly. - var currSort = 0; - foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) - { - Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); - currSort++; - } - //ensure the parent node's properties still exist first - Assert.AreEqual("content", parentNode.ChildNodes[0].Name); - Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); - //then the child nodes should come straight after - Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); - - } - - Debug.WriteLine("Total time for " + iterations + " iterations is " + totalTime); - } - - - [Test] - public void Sort_Nodes() - { - var xmlContent = GetXmlContent(1); - var xml = new XmlDocument(); - xml.LoadXml(xmlContent); - var original = xml.GetElementById(1173.ToString()); - Assert.IsNotNull(original); - - var parentNode = (XmlElement)original.Clone(); - - XmlHelper.SortNodes( - parentNode, - "./* [@id]", - x => x.AttributeValue("sortOrder")); - - //do assertions just to make sure it is working properly. - var currSort = 0; - foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) - { - Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); - currSort++; - } - //ensure the parent node's properties still exist first - Assert.AreEqual("content", parentNode.ChildNodes[0].Name); - Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); - //then the child nodes should come straight after - Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); - } - - /// - /// This was the logic to sort before and now lives here just to show the benchmarks tests above. - /// - /// - private static void LegacySortNodes(ref XmlNode parentNode) - { - XmlNode n = parentNode.CloneNode(true); - - // remove all children from original node - string xpath = "./* [@id]"; - foreach (XmlNode child in parentNode.SelectNodes(xpath)) - parentNode.RemoveChild(child); - - - XPathNavigator nav = n.CreateNavigator(); - XPathExpression expr = nav.Compile(xpath); - expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); - XPathNodeIterator iterator = nav.Select(expr); - while (iterator.MoveNext()) - parentNode.AppendChild( - ((IHasXmlNode)iterator.Current).GetNode()); - } - - /// - /// returns xml with a reverse sort order - /// - /// - /// - private string GetXmlContent(int templateId) - { - return @" - - - - -]> - - - - - 1 - - This is some content]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - } - } -} diff --git a/src/Umbraco.Tests/Security/AuthenticationExtensionsTests.cs b/src/Umbraco.Tests/Security/AuthenticationExtensionsTests.cs new file mode 100644 index 0000000000..e622458c39 --- /dev/null +++ b/src/Umbraco.Tests/Security/AuthenticationExtensionsTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Identity; +using NUnit.Framework; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + public class AuthenticationExtensionsTests + { + [Test] + public void ToErrorMessage_When_Errors_Are_Null_Expect_ArgumentNullException() + { + IEnumerable errors = null; + + Assert.Throws(() => errors.ToErrorMessage()); + } + + [Test] + public void ToErrorMessage_When_Single_Error_Expect_Error_Description() + { + const string expectedError = "invalid something"; + var errors = new List {new IdentityError {Code = "1", Description = expectedError}}; + + var errorMessage = errors.ToErrorMessage(); + + Assert.AreEqual(expectedError, errorMessage); + } + + [Test] + public void ToErrorMessage_When_Multiple_Errors_Expect_Error_Descriptions_With_Comma_Delimiter() + { + const string error1 = "invalid something"; + const string error2 = "invalid something else"; + var errors = new List + { + new IdentityError {Code = "1", Description = error1}, + new IdentityError {Code = "2", Description = error2} + }; + + var errorMessage = errors.ToErrorMessage(); + + Assert.AreEqual($"{error1}, {error2}", errorMessage); + } + } +} diff --git a/src/Umbraco.Tests/Security/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests/Security/BackOfficeClaimsPrincipalFactoryTests.cs new file mode 100644 index 0000000000..b7b516318c --- /dev/null +++ b/src/Umbraco.Tests/Security/BackOfficeClaimsPrincipalFactoryTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; +using Umbraco.Web.Models.Identity; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + [TestFixture] + public class BackOfficeClaimsPrincipalFactoryTests + { + private BackOfficeIdentityUser _testUser; + private Mock> _mockUserManager; + + [Test] + public void Ctor_When_UserManager_Is_Null_Expect_ArgumentNullException() + { + Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + null, + new OptionsWrapper(new IdentityOptions()))); + } + + [Test] + public void Ctor_When_Options_Are_Null_Expect_ArgumentNullException() + { + Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + new Mock>(new Mock>().Object, + null, null, null, null, null, null, null, null).Object, + null)); + } + + [Test] + public void Ctor_When_Options_Value_Is_Null_Expect_ArgumentNullException() + { + Assert.Throws(() => new BackOfficeClaimsPrincipalFactory( + new Mock>(new Mock>().Object, + null, null, null, null, null, null, null, null).Object, + new OptionsWrapper(null))); + } + + [Test] + public void CreateAsync_When_User_Is_Null_Expect_ArgumentNullException() + { + var sut = CreateSut(); + + Assert.ThrowsAsync(async () => await sut.CreateAsync(null)); + } + + [Test] + public async Task CreateAsync_Should_Create_Principal_With_Umbraco_Identity() + { + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + var umbracoBackOfficeIdentity = claimsPrincipal.Identity as UmbracoBackOfficeIdentity; + Assert.IsNotNull(umbracoBackOfficeIdentity); + } + + [Test] + public async Task CreateAsync_Should_Create_NameId() + { + const string expectedClaimType = ClaimTypes.NameIdentifier; + var expectedClaimValue = _testUser.Id.ToString(); + + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); + Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue)); + } + + [Test] + public async Task CreateAsync_Should_Create_Name() + { + const string expectedClaimType = ClaimTypes.Name; + var expectedClaimValue = _testUser.UserName; + + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); + Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue)); + } + + [Test] + public async Task CreateAsync_Should_Create_IdentityProvider() + { + const string expectedClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"; + const string expectedClaimValue = "ASP.NET Identity"; + + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); + Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue)); + } + + [Test] + public async Task CreateAsync_When_SecurityStamp_Supported_Expect_SecurityStamp_Claim() + { + const string expectedClaimType = Constants.Web.SecurityStampClaimType; + var expectedClaimValue = _testUser.SecurityStamp; + + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(_testUser.SecurityStamp); + + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); + Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue)); + } + + [Test] + public async Task CreateAsync_When_Roles_Supported_Expect_Role_Claims_In_UmbracoIdentity() + { + const string expectedClaimType = ClaimTypes.Role; + const string expectedClaimValue = "b87309fb-4caf-48dc-b45a-2b752d051508"; + + _testUser.Roles.Add(new Core.Models.Identity.IdentityUserRole{RoleId = expectedClaimValue}); + _mockUserManager.Setup(x => x.SupportsUserRole).Returns(true); + _mockUserManager.Setup(x => x.GetRolesAsync(_testUser)).ReturnsAsync(new[] {expectedClaimValue}); + + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); + } + + [Test] + public async Task CreateAsync_When_UserClaims_Supported_Expect_UserClaims_In_Actor() + { + const string expectedClaimType = "custom"; + const string expectedClaimValue = "val"; + + _testUser.Claims.Add(new Core.Models.Identity.IdentityUserClaim {ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); + _mockUserManager.Setup(x => x.SupportsUserClaim).Returns(true); + _mockUserManager.Setup(x => x.GetClaimsAsync(_testUser)).ReturnsAsync( + new List {new Claim(expectedClaimType, expectedClaimValue)}); + + var sut = CreateSut(); + + var claimsPrincipal = await sut.CreateAsync(_testUser); + + Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue)); + } + + [SetUp] + public void Setup() + { + var mockGlobalSettings = new Mock(); + mockGlobalSettings.Setup(x => x.DefaultUILanguage).Returns("test"); + + _testUser = new BackOfficeIdentityUser(mockGlobalSettings.Object, 2, new List()) + { + UserName = "bob", + Name = "Bob", + Email = "bob@umbraco.test", + SecurityStamp = "B6937738-9C17-4C7D-A25A-628A875F5177" + }; + + _mockUserManager = new Mock>(new Mock>().Object, + null, null, null, null, null, null, null, null); + _mockUserManager.Setup(x => x.GetUserIdAsync(_testUser)).ReturnsAsync(_testUser.Id.ToString); + _mockUserManager.Setup(x => x.GetUserNameAsync(_testUser)).ReturnsAsync(_testUser.UserName); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); + _mockUserManager.Setup(x => x.SupportsUserClaim).Returns(false); + _mockUserManager.Setup(x => x.SupportsUserRole).Returns(false); + } + + private BackOfficeClaimsPrincipalFactory CreateSut() + { + return new BackOfficeClaimsPrincipalFactory(_mockUserManager.Object, + new OptionsWrapper(new IdentityOptions())); + } + } +} diff --git a/src/Umbraco.Tests/Security/BackOfficeUserManagerTests.cs b/src/Umbraco.Tests/Security/BackOfficeUserManagerTests.cs new file mode 100644 index 0000000000..30ed101297 --- /dev/null +++ b/src/Umbraco.Tests/Security/BackOfficeUserManagerTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Owin.Security.DataProtection; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; +using Umbraco.Net; +using Umbraco.Web.Models.Identity; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + public class BackOfficeUserManagerTests + { + [Test] + public async Task CheckPasswordAsync_When_Default_Password_Hasher_Validates_Umbraco7_Hash_Expect_Valid_Password() + { + const string v7Hash = "7Uob6fMTTxDIhWGebYiSxg==P+hgvWlXLbDd4cFLADn811KOaVI/9pg1PNvTuG5NklY="; + const string plaintext = "4XxzH3s3&J"; + + var mockPasswordConfiguration = new Mock(); + var mockIpResolver = new Mock(); + var mockUserStore = new Mock>(); + var mockDataProtectionProvider = new Mock(); + + mockDataProtectionProvider.Setup(x => x.Create(It.IsAny())) + .Returns(new Mock().Object); + mockPasswordConfiguration.Setup(x => x.HashAlgorithmType) + .Returns("HMACSHA256"); + + var userManager = BackOfficeUserManager.Create( + mockPasswordConfiguration.Object, + mockIpResolver.Object, + mockUserStore.Object, + null, + mockDataProtectionProvider.Object, + new NullLogger>()); + + var mockGlobalSettings = new Mock(); + mockGlobalSettings.Setup(x => x.DefaultUILanguage).Returns("test"); + + var user = new BackOfficeIdentityUser(mockGlobalSettings.Object, 2, new List()) + { + UserName = "alice", + Name = "Alice", + Email = "alice@umbraco.test", + PasswordHash = v7Hash + }; + + mockUserStore.Setup(x => x.GetPasswordHashAsync(user, It.IsAny())) + .ReturnsAsync(v7Hash); + + var isValidPassword = await userManager.CheckPasswordAsync(user, plaintext); + + Assert.True(isValidPassword); + } + } +} diff --git a/src/Umbraco.Tests/Security/NopLookupNormalizerTests.cs b/src/Umbraco.Tests/Security/NopLookupNormalizerTests.cs new file mode 100644 index 0000000000..2abecbb4dd --- /dev/null +++ b/src/Umbraco.Tests/Security/NopLookupNormalizerTests.cs @@ -0,0 +1,56 @@ +using System; +using NUnit.Framework; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + public class NopLookupNormalizerTests + { + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void NormalizeName_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string name) + { + var sut = new NopLookupNormalizer(); + + var normalizedName = sut.NormalizeName(name); + + Assert.AreEqual(name, normalizedName); + } + + [Test] + public void NormalizeName_Expect_Input_Returned() + { + var name = Guid.NewGuid().ToString(); + var sut = new NopLookupNormalizer(); + + var normalizedName = sut.NormalizeName(name); + + Assert.AreEqual(name, normalizedName); + } + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void NormalizeEmail_When_Name_Null_Or_Whitespace_Expect_Same_Returned(string email) + { + var sut = new NopLookupNormalizer(); + + var normalizedEmail = sut.NormalizeEmail(email); + + Assert.AreEqual(email, normalizedEmail); + } + + [Test] + public void NormalizeEmail_Expect_Input_Returned() + { + var email = $"{Guid.NewGuid()}@umbraco"; + var sut = new NopLookupNormalizer(); + + var normalizedEmail = sut.NormalizeEmail(email); + + Assert.AreEqual(email, normalizedEmail); + } + } +} diff --git a/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs b/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs new file mode 100644 index 0000000000..5e9b731aa9 --- /dev/null +++ b/src/Umbraco.Tests/Security/OwinDataProtectorTokenProviderTests.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Owin.Security.DataProtection; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.Identity; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + public class OwinDataProtectorTokenProviderTests + { + private Mock _mockDataProtector; + private Mock> _mockUserManager; + private BackOfficeIdentityUser _testUser; + private const string _testPurpose = "test"; + + [Test] + public void Ctor_When_Protector_Is_Null_Expect_ArgumentNullException() + { + Assert.Throws(() => new OwinDataProtectorTokenProvider(null)); + } + + [Test] + public async Task CanGenerateTwoFactorTokenAsync_Expect_False() + { + var sut = CreateSut(); + + var canGenerate = await sut.CanGenerateTwoFactorTokenAsync(_mockUserManager.Object, _testUser); + + Assert.False(canGenerate); + } + + [Test] + public void GenerateAsync_When_UserManager_Is_Null_Expect_ArgumentNullException() + { + var sut = CreateSut(); + Assert.ThrowsAsync(async () => await sut.GenerateAsync(null, null, _testUser)); + } + + [Test] + public void GenerateAsync_When_User_Is_Null_Expect_ArgumentNullException() + { + var sut = CreateSut(); + Assert.ThrowsAsync(async () => await sut.GenerateAsync(null, _mockUserManager.Object, null)); + } + + [Test] + public async Task GenerateAsync_When_Token_Generated_Expect_Ticks_And_User_ID() + { + var sut = CreateSut(); + + var token = await sut.GenerateAsync(null, _mockUserManager.Object, _testUser); + + using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) + { + var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); + var foundUserId = reader.ReadString(); + + Assert.That(creationTime.DateTime, Is.EqualTo(DateTime.UtcNow).Within(1).Minutes); + Assert.AreEqual(_testUser.Id.ToString(), foundUserId); + } + } + + [Test] + public async Task GenerateAsync_When_Token_Generated_With_Purpose_Expect_Purpose_In_Token() + { + var expectedPurpose = Guid.NewGuid().ToString(); + + var sut = CreateSut(); + + var token = await sut.GenerateAsync(expectedPurpose, _mockUserManager.Object, _testUser); + + using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) + { + reader.ReadInt64(); // creation time + reader.ReadString(); // user ID + var purpose = reader.ReadString(); + + Assert.AreEqual(expectedPurpose, purpose); + } + } + + [Test] + public async Task GenerateAsync_When_Token_Generated_And_SecurityStamp_Supported_Expect_SecurityStamp_In_Token() + { + var expectedSecurityStamp = Guid.NewGuid().ToString(); + + var sut = CreateSut(); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(expectedSecurityStamp); + + var token = await sut.GenerateAsync(null, _mockUserManager.Object, _testUser); + + using (var reader = new BinaryReader(new MemoryStream(Convert.FromBase64String(token)))) + { + reader.ReadInt64(); // creation time + reader.ReadString(); // user ID + reader.ReadString(); // purpose + var securityStamp = reader.ReadString(); + + Assert.AreEqual(expectedSecurityStamp, securityStamp); + } + } + + [Test] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + public void ValidateAsync_When_Token_Is_Null_Or_Whitespace_Expect_ArgumentNullException(string token) + { + var sut = CreateSut(); + + Assert.ThrowsAsync(() => sut.ValidateAsync(null, token, _mockUserManager.Object, _testUser)); + } + + [Test] + public void ValidateAsync_When_UserManager_Is_Null_Expect_ArgumentNullException() + { + var sut = CreateSut(); + + Assert.ThrowsAsync(() => sut.ValidateAsync(null, Guid.NewGuid().ToString(), null, _testUser)); + } + + [Test] + public void ValidateAsync_When_User_Is_Null_Expect_ArgumentNullException() + { + var sut = CreateSut(); + + Assert.ThrowsAsync(() => sut.ValidateAsync(null, Guid.NewGuid().ToString(), _mockUserManager.Object, null)); + } + + [Test] + public async Task ValidateAsync_When_Token_Has_Expired_Expect_False() + { + var sut = CreateSut(); + var testToken = CreateTestToken(creationDate: DateTime.UtcNow.AddYears(-10)); + + var isValid = await sut.ValidateAsync(null, testToken, _mockUserManager.Object, _testUser); + + Assert.False(isValid); + } + + [Test] + public async Task ValidateAsync_When_Token_Was_Issued_To_Wrong_User_Expect_False() + { + var sut = CreateSut(); + var testToken = CreateTestToken(userId: Guid.NewGuid().ToString()); + + var isValid = await sut.ValidateAsync(_testPurpose, testToken, _mockUserManager.Object, _testUser); + + Assert.False(isValid); + } + + [Test] + public async Task ValidateAsync_When_Token_Was_Has_Wrong_Purpose_Expect_False() + { + var sut = CreateSut(); + var testToken = CreateTestToken(purpose: "invalid"); + + var isValid = await sut.ValidateAsync("valid", testToken, _mockUserManager.Object, _testUser); + + Assert.False(isValid); + } + + [Test] + public async Task ValidateAsync_When_Token_Was_Has_Wrong_SecurityStamp_Expect_False() + { + var sut = CreateSut(); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(Guid.NewGuid().ToString); + + var testToken = CreateTestToken(securityStamp: "invalid"); + + var isValid = await sut.ValidateAsync(_testPurpose, testToken, _mockUserManager.Object, _testUser); + + Assert.False(isValid); + } + + [Test] + public async Task ValidateAsync_When_Valid_Token_Expect_True() + { + const string validPurpose = "test"; + var validSecurityStamp = Guid.NewGuid().ToString(); + + var sut = CreateSut(); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(validSecurityStamp); + + var testToken = CreateTestToken( + creationDate: DateTime.UtcNow, + userId: _testUser.Id.ToString(), + purpose: validPurpose, + securityStamp: validSecurityStamp); + + var isValid = await sut.ValidateAsync(validPurpose, testToken, _mockUserManager.Object, _testUser); + + Assert.True(isValid); + } + + private OwinDataProtectorTokenProvider CreateSut() + => new OwinDataProtectorTokenProvider(_mockDataProtector.Object); + + private string CreateTestToken(DateTime? creationDate = null, string userId = null, string purpose = null, string securityStamp = null) + { + var ms = new MemoryStream(); + using (var writer = new BinaryWriter(ms)) + { + writer.Write(creationDate?.Ticks ?? DateTimeOffset.UtcNow.UtcTicks); + writer.Write(userId ?? _testUser.Id.ToString()); + writer.Write(purpose ?? _testPurpose); + writer.Write(securityStamp ?? ""); + } + + return Convert.ToBase64String(ms.ToArray()); + } + + [SetUp] + public void Setup() + { + _mockDataProtector = new Mock(); + _mockDataProtector.Setup(x => x.Protect(It.IsAny())).Returns((byte[] originalBytes) => originalBytes); + _mockDataProtector.Setup(x => x.Unprotect(It.IsAny())).Returns((byte[] originalBytes) => originalBytes); + + var mockGlobalSettings = new Mock(); + mockGlobalSettings.Setup(x => x.DefaultUILanguage).Returns("test"); + + _mockUserManager = new Mock>(new Mock>().Object, + null, null, null, null, null, null, null, null); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); + + _testUser = new BackOfficeIdentityUser(mockGlobalSettings.Object, 2, new List()) + { + UserName = "alice", + Name = "Alice", + Email = "alice@umbraco.test", + SecurityStamp = Guid.NewGuid().ToString() + }; + } + } +} diff --git a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs index beb2c0b3dc..9c16d0c35a 100644 --- a/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests/Security/UmbracoBackOfficeIdentityTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Security new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(Constants.Security.SessionIdClaimType, sessionId, Constants.Security.SessionIdClaimType, TestIssuer, TestIssuer), new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer), - new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer), + new Claim(Constants.Web.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer), }); var backofficeIdentity = UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity); diff --git a/src/Umbraco.Tests/Security/UmbracoSecurityStampValidatorTests.cs b/src/Umbraco.Tests/Security/UmbracoSecurityStampValidatorTests.cs new file mode 100644 index 0000000000..92fa032271 --- /dev/null +++ b/src/Umbraco.Tests/Security/UmbracoSecurityStampValidatorTests.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Owin; +using Microsoft.Owin.Logging; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Cookies; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.Membership; +using Umbraco.Net; +using Umbraco.Web.Models.Identity; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + public class UmbracoSecurityStampValidatorTests + { + private Mock _mockOwinContext; + private Mock> _mockUserManager; + private Mock _mockSignInManager; + + private AuthenticationTicket _testAuthTicket; + private CookieAuthenticationOptions _testOptions; + private BackOfficeIdentityUser _testUser; + private const string _testAuthType = "cookie"; + + [Test] + public void OnValidateIdentity_When_GetUserIdCallback_Is_Null_Expect_ArgumentNullException() + { + Assert.Throws(() => UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MaxValue, null, null)); + } + + [Test] + public async Task OnValidateIdentity_When_Validation_Interval_Not_Met_Expect_No_Op() + { + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MaxValue, null, identity => throw new Exception()); + + _testAuthTicket.Properties.IssuedUtc = DateTimeOffset.UtcNow; + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + await func(context); + + Assert.AreEqual(_testAuthTicket.Identity, context.Identity); + } + + [Test] + public void OnValidateIdentity_When_Time_To_Validate_But_No_UserManager_Expect_InvalidOperationException() + { + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, null, identity => throw new Exception()); + + _mockOwinContext.Setup(x => x.Get>(It.IsAny())) + .Returns((BackOfficeUserManager) null); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + Assert.ThrowsAsync(async () => await func(context)); + } + + [Test] + public void OnValidateIdentity_When_Time_To_Validate_But_No_SignInManager_Expect_InvalidOperationException() + { + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, null, identity => throw new Exception()); + + _mockOwinContext.Setup(x => x.Get(It.IsAny())) + .Returns((BackOfficeSignInManager) null); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + Assert.ThrowsAsync(async () => await func(context)); + } + + [Test] + public async Task OnValidateIdentity_When_Time_To_Validate_And_User_No_Longer_Found_Expect_Rejected() + { + var userId = Guid.NewGuid().ToString(); + + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, null, identity => userId); + + _mockUserManager.Setup(x => x.FindByIdAsync(userId)) + .ReturnsAsync((BackOfficeIdentityUser) null); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + await func(context); + + Assert.IsNull(context.Identity); + _mockOwinContext.Verify(x => x.Authentication.SignOut(_testAuthType), Times.Once); + } + + [Test] + public async Task OnValidateIdentity_When_Time_To_Validate_And_User_Exists_And_Does_Not_Support_SecurityStamps_Expect_Rejected() + { + var userId = Guid.NewGuid().ToString(); + + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, null, identity => userId); + + _mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + await func(context); + + Assert.IsNull(context.Identity); + _mockOwinContext.Verify(x => x.Authentication.SignOut(_testAuthType), Times.Once); + } + + [Test] + public async Task OnValidateIdentity_When_Time_To_Validate_And_SecurityStamp_Has_Changed_Expect_Rejected() + { + var userId = Guid.NewGuid().ToString(); + + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, null, identity => userId); + + _mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(Guid.NewGuid().ToString); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + await func(context); + + Assert.IsNull(context.Identity); + _mockOwinContext.Verify(x => x.Authentication.SignOut(_testAuthType), Times.Once); + } + + [Test] + public async Task OnValidateIdentity_When_Time_To_Validate_And_SecurityStamp_Has_Not_Changed_Expect_No_Change() + { + var userId = Guid.NewGuid().ToString(); + + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, null, identity => userId); + + _mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(_testUser.SecurityStamp); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + await func(context); + + Assert.AreEqual(_testAuthTicket.Identity, context.Identity); + } + + [Test] + public async Task OnValidateIdentity_When_User_Validated_And_RegenerateIdentityCallback_Present_Expect_User_Refreshed() + { + var userId = Guid.NewGuid().ToString(); + var expectedIdentity = new ClaimsIdentity(new List {new Claim("sub", "bob")}); + + var regenFuncCalled = false; + Func, BackOfficeIdentityUser, Task> regenFunc = + (signInManager, userManager, user) => + { + regenFuncCalled = true; + return Task.FromResult(expectedIdentity); + }; + + var func = UmbracoSecurityStampValidator + .OnValidateIdentity, BackOfficeIdentityUser>( + TimeSpan.MinValue, regenFunc, identity => userId); + + _mockUserManager.Setup(x => x.FindByIdAsync(userId)).ReturnsAsync(_testUser); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); + _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(_testUser.SecurityStamp); + + var context = new CookieValidateIdentityContext( + _mockOwinContext.Object, + _testAuthTicket, + _testOptions); + + ClaimsIdentity callbackIdentity = null; + _mockOwinContext.Setup(x => x.Authentication.SignIn(context.Properties, It.IsAny())) + .Callback((AuthenticationProperties props, ClaimsIdentity[] identities) => callbackIdentity = identities.FirstOrDefault()) + .Verifiable(); + + await func(context); + + Assert.True(regenFuncCalled); + Assert.AreEqual(expectedIdentity, callbackIdentity); + Assert.IsNull(context.Properties.IssuedUtc); + Assert.IsNull(context.Properties.ExpiresUtc); + + _mockOwinContext.Verify(); + } + + [SetUp] + public void Setup() + { + var mockGlobalSettings = new Mock(); + mockGlobalSettings.Setup(x => x.DefaultUILanguage).Returns("test"); + + _testUser = new BackOfficeIdentityUser(mockGlobalSettings.Object, 2, new List()) + { + UserName = "alice", + Name = "Alice", + Email = "alice@umbraco.test", + SecurityStamp = Guid.NewGuid().ToString() + }; + + _testAuthTicket = new AuthenticationTicket( + new ClaimsIdentity( + new List {new Claim("sub", "alice"), new Claim(Constants.Web.SecurityStampClaimType, _testUser.SecurityStamp)}, + _testAuthType), + new AuthenticationProperties()); + _testOptions = new CookieAuthenticationOptions { AuthenticationType = _testAuthType }; + + _mockUserManager = new Mock>( + new Mock().Object, + new Mock().Object, + new Mock>().Object, + null, null, null, null, null, null, null); + _mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny())).ReturnsAsync((BackOfficeIdentityUser) null); + _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(false); + + _mockSignInManager = new Mock( + _mockUserManager.Object, + new Mock>().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object, + new Mock().Object); + + _mockOwinContext = new Mock(); + _mockOwinContext.Setup(x => x.Get>(It.IsAny())) + .Returns(_mockUserManager.Object); + _mockOwinContext.Setup(x => x.Get(It.IsAny())) + .Returns(_mockSignInManager.Object); + + _mockOwinContext.Setup(x => x.Authentication.SignOut(It.IsAny())); + } + } +} diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 0d98574504..ab9f85aa3c 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -99,8 +99,8 @@ namespace Umbraco.Tests.Services private void AssertJsonStartsWith(int id, string expected) { var json = GetJson(id).Replace('"', '\''); - var pos = json.IndexOf("'cultureData':", StringComparison.InvariantCultureIgnoreCase); - json = json.Substring(0, pos + "'cultureData':".Length); + var pos = json.IndexOf("'cd':", StringComparison.InvariantCultureIgnoreCase); + json = json.Substring(0, pos + "'cd':".Length); Assert.AreEqual(expected, json); } @@ -606,7 +606,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch content type to Nothing contentType.Variations = ContentVariation.Nothing; @@ -623,7 +623,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch content back to Culture contentType.Variations = ContentVariation.Culture; @@ -640,7 +640,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch property back to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -656,7 +656,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); } [Test] @@ -697,7 +697,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch content type to Culture contentType.Variations = ContentVariation.Culture; @@ -713,7 +713,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch property to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -728,7 +728,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); // switch content back to Nothing contentType.Variations = ContentVariation.Nothing; @@ -745,7 +745,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':"); } [Test] @@ -783,7 +783,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch property type to Nothing contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; @@ -800,7 +800,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':"); // switch property back to Culture contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; @@ -816,7 +816,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':"); // switch other property to Culture contentType.PropertyTypes.First(x => x.Alias == "value2").Variations = ContentVariation.Culture; @@ -834,7 +834,7 @@ namespace Umbraco.Tests.Services Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':"); + "{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'c':'en','v':'v2'}]},'cd':"); } [TestCase(ContentVariation.Culture, ContentVariation.Nothing)] @@ -1065,7 +1065,7 @@ namespace Umbraco.Tests.Services // both value11 and value21 are variant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composed.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composed); @@ -1073,7 +1073,7 @@ namespace Umbraco.Tests.Services // both value11 and value21 are invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); composed.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed); @@ -1081,7 +1081,7 @@ namespace Umbraco.Tests.Services // value11 is variant again, but value21 is still invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed); @@ -1089,7 +1089,7 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composing); @@ -1097,7 +1097,7 @@ namespace Umbraco.Tests.Services // value11 is invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1105,7 +1105,7 @@ namespace Umbraco.Tests.Services // value11 is still invariant Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1113,7 +1113,7 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document.Id)); AssertJsonStartsWith(document.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); } [Test] @@ -1178,11 +1178,11 @@ namespace Umbraco.Tests.Services // both value11 and value21 are variant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composed1); @@ -1190,11 +1190,11 @@ namespace Umbraco.Tests.Services // both value11 and value21 are invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed1); @@ -1202,11 +1202,11 @@ namespace Umbraco.Tests.Services // value11 is variant again, but value21 is still invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composed1.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composed1); @@ -1214,11 +1214,11 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(composing); @@ -1226,11 +1226,11 @@ namespace Umbraco.Tests.Services // value11 is invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1238,11 +1238,11 @@ namespace Umbraco.Tests.Services // value11 is still invariant Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(composing); @@ -1250,11 +1250,11 @@ namespace Umbraco.Tests.Services // we can make it variant again Console.WriteLine(GetJson(document1.Id)); AssertJsonStartsWith(document1.Id, - "{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':"); + "{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':"); Console.WriteLine(GetJson(document2.Id)); AssertJsonStartsWith(document2.Id, - "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); + "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); } private void CreateFrenchAndEnglishLangs() diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 80f6ab9c9e..bbc869fc65 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -313,6 +313,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); + Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3a32ee17fc..9bc7e71fad 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -122,6 +122,7 @@ + @@ -143,7 +144,13 @@ + + + + + + @@ -176,8 +183,6 @@ - - @@ -366,7 +371,6 @@ - @@ -452,7 +456,6 @@ - @@ -471,7 +474,6 @@ - diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index a162b0cd48..f8f02560c4 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -91,7 +91,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index e3c2a89593..ad79391380 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -509,7 +509,7 @@ namespace Umbraco.Tests.Web.Controllers var display = JsonConvert.DeserializeObject(response.Item2); Assert.AreEqual(2, display.Errors.Count()); Assert.IsTrue(display.Errors.ContainsKey("Variants[0].Name")); - Assert.IsTrue(display.Errors.ContainsKey("_content_variant_en-US_")); + Assert.IsTrue(display.Errors.ContainsKey("_content_variant_en-US_null_")); } // TODO: There are SOOOOO many more tests we should write - a lot of them to do with validation diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index 563d9b80f8..ce355180f6 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -1,12 +1,19 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Net; using System.Net.Http; using System.Net.Http.Formatting; using System.Reflection; using System.Security.Cryptography; +using System.Threading.Tasks; using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.Owin; using Moq; using Newtonsoft.Json; using NUnit.Framework; @@ -19,12 +26,10 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.ControllerTesting; using Umbraco.Tests.TestHelpers.Entities; @@ -39,6 +44,9 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; using Umbraco.Web.Routing; using Umbraco.Core.Media; +using Umbraco.Net; +using Umbraco.Web.Models.Identity; +using Umbraco.Web.Security; namespace Umbraco.Tests.Web.Controllers { @@ -62,7 +70,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task Save_User() + public async Task Save_User() { ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) { @@ -95,7 +103,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; @@ -149,7 +158,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetPagedUsers_Empty() + public async Task GetPagedUsers_Empty() { ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) { @@ -168,7 +177,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -183,7 +193,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetPagedUsers_10() + public async Task GetPagedUsers_10() { ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor) { @@ -211,7 +221,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -227,7 +238,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetPagedUsers_Fips() + public async Task GetPagedUsers_Fips() { await RunFipsTest("GetPagedUsers", mock => { @@ -246,7 +257,7 @@ namespace Umbraco.Tests.Web.Controllers } [Test] - public async System.Threading.Tasks.Task GetById_Fips() + public async Task GetById_Fips() { const int mockUserId = 1234; var user = MockedUser.CreateUser(); @@ -264,7 +275,7 @@ namespace Umbraco.Tests.Web.Controllers } - private async System.Threading.Tasks.Task RunFipsTest(string action, Action> userServiceSetup, + private async Task RunFipsTest(string action, Action> userServiceSetup, Action> verification, object routeDefaults = null, string url = null) { @@ -289,7 +300,8 @@ namespace Umbraco.Tests.Web.Controllers Factory.GetInstance(), Factory.GetInstance(), Factory.GetInstance(), - Factory.GetInstance() + Factory.GetInstance(), + Factory.GetInstance() ); return usersController; } @@ -324,5 +336,186 @@ namespace Umbraco.Tests.Web.Controllers } } } + + [Test] + public async Task PostUnlockUsers_When_UserIds_Not_Supplied_Expect_Ok_Response() + { + var usersController = CreateSut(); + + usersController.Request = new HttpRequestMessage(); + + var response = await usersController.PostUnlockUsers(new int[0]); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + } + + [Test] + public void PostUnlockUsers_When_User_Does_Not_Exist_Expect_InvalidOperationException() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny())) + .ReturnsAsync((BackOfficeIdentityUser) null); + + Assert.ThrowsAsync(async () => await usersController.PostUnlockUsers(new[] {1})); + } + + [Test] + public async Task PostUnlockUsers_When_User_Lockout_Update_Fails_Expect_Failure_Response() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + const string expectedMessage = "identity error!"; + var user = new BackOfficeIdentityUser( + new Mock().Object, + 1, + new List()) + { + Name = "bob" + }; + + mockUserManager.Setup(x => x.FindByIdAsync(It.IsAny())) + .ReturnsAsync(user); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user, It.IsAny())) + .ReturnsAsync(IdentityResult.Failed(new IdentityError {Description = expectedMessage})); + + var response = await usersController.PostUnlockUsers(new[] { 1 }); + + Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode); + Assert.True(response.Headers.TryGetValues("X-Status-Reason", out var values)); + Assert.True(values.Contains("Validation failed")); + + var responseContent = response.Content as ObjectContent; + var responseValue = responseContent?.Value as HttpError; + Assert.NotNull(responseValue); + Assert.True(responseValue.Message.Contains(expectedMessage)); + Assert.True(responseValue.Message.Contains(user.Id.ToString())); + } + + [Test] + public async Task PostUnlockUsers_When_One_UserId_Supplied_Expect_User_Locked_Out_With_Correct_Response_Message() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + var user = new BackOfficeIdentityUser( + new Mock().Object, + 1, + new List()) + { + Name = "bob" + }; + + mockUserManager.Setup(x => x.FindByIdAsync(user.Id.ToString())) + .ReturnsAsync(user); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user, It.IsAny())) + .ReturnsAsync(IdentityResult.Success) + .Verifiable(); + + var response = await usersController.PostUnlockUsers(new[] { user.Id }); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + var responseContent = response.Content as ObjectContent; + var notifications = responseContent?.Value as SimpleNotificationModel; + Assert.NotNull(notifications); + Assert.AreEqual(user.Name, notifications.Message); + mockUserManager.Verify(); + } + + [Test] + public async Task PostUnlockUsers_When_Multiple_UserIds_Supplied_Expect_User_Locked_Out_With_Correct_Response_Message() + { + var mockUserManager = CreateMockUserManager(); + var usersController = CreateSut(mockUserManager); + + var user1 = new BackOfficeIdentityUser( + new Mock().Object, + 1, + new List()) + { + Name = "bob" + }; + var user2 = new BackOfficeIdentityUser( + new Mock().Object, + 2, + new List()) + { + Name = "alice" + }; + var userIdsToLock = new[] {user1.Id, user2.Id}; + + mockUserManager.Setup(x => x.FindByIdAsync(user1.Id.ToString())) + .ReturnsAsync(user1); + mockUserManager.Setup(x => x.FindByIdAsync(user2.Id.ToString())) + .ReturnsAsync(user2); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user1, It.IsAny())) + .ReturnsAsync(IdentityResult.Success) + .Verifiable(); + mockUserManager.Setup(x => x.SetLockoutEndDateAsync(user2, It.IsAny())) + .ReturnsAsync(IdentityResult.Success) + .Verifiable(); + + var response = await usersController.PostUnlockUsers(userIdsToLock); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + + var responseContent = response.Content as ObjectContent; + var notifications = responseContent?.Value as SimpleNotificationModel; + Assert.NotNull(notifications); + Assert.AreEqual(userIdsToLock.Length.ToString(), notifications.Message); + mockUserManager.Verify(); + } + + private UsersController CreateSut(IMock> mockUserManager = null) + { + var mockLocalizedTextService = new Mock(); + mockLocalizedTextService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns((string key, CultureInfo ci, IDictionary tokens) + => tokens.Aggregate("", (current, next) => current + (current == string.Empty ? "" : ",") + next.Value)); + + var usersController = new UsersController( + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ServiceContext.CreatePartial(localizedTextService: mockLocalizedTextService.Object), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + ShortStringHelper, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance()); + + var mockOwinContext = new Mock(); + var mockUserManagerMarker = new Mock(); + + mockOwinContext.Setup(x => x.Get(It.IsAny())) + .Returns(mockUserManagerMarker.Object); + mockUserManagerMarker.Setup(x => x.GetManager(It.IsAny())) + .Returns(mockUserManager?.Object ?? CreateMockUserManager().Object); + + usersController.Request = new HttpRequestMessage(); + usersController.Request.Properties["MS_OwinContext"] = mockOwinContext.Object; + usersController.Request.Properties[HttpPropertyKeys.RequestContextKey] = new HttpRequestContext {Configuration = new HttpConfiguration()}; + + return usersController; + } + + private static Mock> CreateMockUserManager() + { + return new Mock>( + new Mock().Object, + new Mock().Object, + new Mock>().Object, + null, null, null, null, null, null, null); + } } } diff --git a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs b/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs index 3ce43b5fc2..7b25e60b5a 100644 --- a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs +++ b/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs @@ -22,19 +22,19 @@ namespace Umbraco.Tests.Web ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property - var result = ms.GetCulturesWithErrors(localizationService.Object, "en-US"); + var result = ms.GetVariantsWithErrors("en-US"); //even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property Assert.AreEqual(1, result.Count); - Assert.AreEqual("en-US", result[0]); + Assert.AreEqual("en-US", result[0].culture); ms = new ModelStateDictionary(); - ms.AddCultureValidationError("en-US", "generic culture error"); + ms.AddVariantValidationError("en-US", null, "generic culture error"); - result = ms.GetCulturesWithErrors(localizationService.Object, "en-US"); + result = ms.GetVariantsWithErrors("en-US"); Assert.AreEqual(1, result.Count); - Assert.AreEqual("en-US", result[0]); + Assert.AreEqual("en-US", result[0].culture); } [Test] @@ -47,11 +47,11 @@ namespace Umbraco.Tests.Web ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property - var result = ms.GetCulturesWithPropertyErrors(localizationService.Object, "en-US"); + var result = ms.GetVariantsWithPropertyErrors("en-US"); //even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property Assert.AreEqual(1, result.Count); - Assert.AreEqual("en-US", result[0]); + Assert.AreEqual("en-US", result[0].culture); } [Test] @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Web ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property - Assert.AreEqual("_Properties.headerImage.invariant", ms.Keys.First()); + Assert.AreEqual("_Properties.headerImage.invariant.null", ms.Keys.First()); } [Test] @@ -73,9 +73,57 @@ namespace Umbraco.Tests.Web var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //invariant property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //variant property - Assert.AreEqual("_Properties.headerImage.en-US", ms.Keys.First()); + Assert.AreEqual("_Properties.headerImage.en-US.null", ms.Keys.First()); + } + + [Test] + public void Add_Invariant_Segment_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null, "mySegment"); //invariant/segment property + + Assert.AreEqual("_Properties.headerImage.invariant.mySegment", ms.Keys.First()); + } + + [Test] + public void Add_Variant_Segment_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US", "mySegment"); //variant/segment property + + Assert.AreEqual("_Properties.headerImage.en-US.mySegment", ms.Keys.First()); + } + + [Test] + public void Add_Invariant_Segment_Field_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", null, "mySegment"); //invariant/segment property + + Assert.AreEqual("_Properties.headerImage.invariant.mySegment.myField", ms.Keys.First()); + } + + [Test] + public void Add_Variant_Segment_Field_Property_Error() + { + var ms = new ModelStateDictionary(); + var localizationService = new Mock(); + localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); + + ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", "en-US", "mySegment"); //variant/segment property + + Assert.AreEqual("_Properties.headerImage.en-US.mySegment.myField", ms.Keys.First()); } } } diff --git a/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs new file mode 100644 index 0000000000..50ef8cf906 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs @@ -0,0 +1,79 @@ +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// Appends a custom response header to notify the UI that the current user data has been modified + /// + public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute + { + private readonly string _userIdParameter; + + /// + /// An empty constructor which will always set the header. + /// + public AppendUserModifiedHeaderAttribute() + { + } + + /// + /// A constructor specifying the action parameter name containing the user id to match against the + /// current user and if they match the header will be appended. + /// + /// + public AppendUserModifiedHeaderAttribute(string userIdParameter) + { + _userIdParameter = userIdParameter ?? throw new ArgumentNullException(nameof(userIdParameter)); + } + + public override void OnActionExecuting(ActionExecutingContext context) + { + if (_userIdParameter.IsNullOrWhiteSpace()) + { + AppendHeader(context); + } + else + { + if (!context.ActionArguments.ContainsKey(_userIdParameter)) + { + throw new InvalidOperationException($"No argument found for the current action with the name: {_userIdParameter}"); + } + + var umbracoContextAccessor = context.HttpContext.RequestServices.GetService(); + var user = umbracoContextAccessor.UmbracoContext.Security.CurrentUser; + if (user == null) + { + return; + } + + var userId = GetUserIdFromParameter(context.ActionArguments[_userIdParameter]); + if (userId == user.Id) + { + AppendHeader(context); + } + } + } + + public static void AppendHeader(ActionExecutingContext context) + { + const string HeaderName = "X-Umb-User-Modified"; + if (context.HttpContext.Response.Headers.ContainsKey(HeaderName) == false) + { + context.HttpContext.Response.Headers.Add(HeaderName, "1"); + } + } + + private int GetUserIdFromParameter(object parameterValue) + { + if (parameterValue is int) + { + return (int)parameterValue; + } + + throw new InvalidOperationException($"The id type: {parameterValue.GetType()} is not a supported id."); + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs new file mode 100644 index 0000000000..9ee289a39c --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Web.Common.Extensions; + +namespace Umbraco.Web.BackOffice.Filters +{ + public class OnlyLocalRequestsAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + if (!context.HttpContext.Request.IsLocal()) + { + context.Result = new NotFoundResult(); + } + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/ValidationFilterAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ValidationFilterAttribute.cs new file mode 100644 index 0000000000..77d44062d0 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/ValidationFilterAttribute.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// An action filter used to do basic validation against the model and return a result + /// straight away if it fails. + /// + internal sealed class ValidationFilterAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + var modelState = context.ModelState; + if (!modelState.IsValid) + { + context.Result = new BadRequestObjectResult(modelState); + } + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 5b031c095e..3f6dd6969c 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -14,6 +14,12 @@ + + + <_Parameter1>Umbraco.Tests.UnitTests + + + diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs new file mode 100644 index 0000000000..c346f0ddc9 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -0,0 +1,42 @@ +using System.Net; +using Microsoft.AspNetCore.Http; + +namespace Umbraco.Web.Common.Extensions +{ + public static class HttpRequestExtensions + { + internal static string ClientCulture(this HttpRequest request) + { + return request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null; + } + + /// + /// Determines if a request is local. + /// + /// True if request is local + /// + /// Hat-tip: https://stackoverflow.com/a/41242493/489433 + /// + public static bool IsLocal(this HttpRequest request) + { + var connection = request.HttpContext.Connection; + if (connection.RemoteIpAddress.IsSet()) + { + // We have a remote address set up + return connection.LocalIpAddress.IsSet() + // Is local is same as remote, then we are local + ? connection.RemoteIpAddress.Equals(connection.LocalIpAddress) + // else we are remote if the remote IP address is not a loopback address + : IPAddress.IsLoopback(connection.RemoteIpAddress); + } + + return true; + } + + private static bool IsSet(this IPAddress address) + { + const string NullIpAddress = "::1"; + return address != null && address.ToString() != NullIpAddress; + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 0fe784f421..d9e71fa666 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -176,7 +176,7 @@ namespace Umbraco.Web.Common.Extensions var connStrings = configs.ConnectionStrings(); var appSettingMainDomLock = globalSettings.MainDomLock; var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" - ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator) + ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator, hostingEnvironment) : new MainDomSemaphoreLock(logger, hostingEnvironment); var mainDom = new MainDom(logger, mainDomLock); diff --git a/src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs new file mode 100644 index 0000000000..559a02e149 --- /dev/null +++ b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs @@ -0,0 +1,76 @@ +using System; +using System.Net; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.ModelBinders; + +namespace Umbraco.Web.Common.Filters +{ + /// + /// An exception filter checking if we get a or with the same model. + /// In which case it returns a redirect to the same page after 1 sec if not in debug mode. + /// + /// + /// This is only enabled when running PureLive + /// + internal class ModelBindingExceptionFilter : ActionFilterAttribute, IExceptionFilter + { + private static readonly Regex _getPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled); + + private readonly IExceptionFilterSettings _exceptionFilterSettings; + private readonly IPublishedModelFactory _publishedModelFactory; + + public ModelBindingExceptionFilter(IExceptionFilterSettings exceptionFilterSettings, IPublishedModelFactory publishedModelFactory) + { + _exceptionFilterSettings = exceptionFilterSettings; + _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); + } + + public void OnException(ExceptionContext filterContext) + { + var disabled = _exceptionFilterSettings?.Disabled ?? false; + if (_publishedModelFactory.IsLiveFactory() + && !disabled + && !filterContext.ExceptionHandled + && ((filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException) + && IsMessageAboutTheSameModelType(filterContext.Exception.Message))) + { + filterContext.HttpContext.Response.Headers.Add(HttpResponseHeader.RetryAfter.ToString(), "1"); + filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.GetEncodedUrl(), false); + + filterContext.ExceptionHandled = true; + } + } + + /// + /// Returns true if the message is about two models with the same name. + /// + /// + /// Message could be something like: + /// + /// InvalidCastException: + /// [A]Umbraco.Web.PublishedModels.Home cannot be cast to [B]Umbraco.Web.PublishedModels.Home. Type A originates from 'App_Web_all.generated.cs.8f9494c4.rtdigm_z, Version=0.0.0.3, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rtdigm_z.dll'. Type B originates from 'App_Web_all.generated.cs.8f9494c4.rbyqlplu, Version=0.0.0.5, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rbyqlplu.dll'. + /// + /// + /// ModelBindingException: + /// Cannot bind source content type Umbraco.Web.PublishedModels.Home to model type Umbraco.Web.PublishedModels.Home. Both view and content models are PureLive, with different versions. The application is in an unstable state and is going to be restarted. The application is restarting now. + /// + /// + private bool IsMessageAboutTheSameModelType(string exceptionMessage) + { + var matches = _getPublishedModelsTypesRegex.Matches(exceptionMessage); + + if (matches.Count >= 2) + { + return string.Equals(matches[0].Value, matches[1].Value, StringComparison.InvariantCulture); + } + + return false; + } + } +} diff --git a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs new file mode 100644 index 0000000000..43d9df4586 --- /dev/null +++ b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs @@ -0,0 +1,192 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Common.ModelBinders +{ + /// + /// Maps view models, supporting mapping to and from any IPublishedContent or IContentModel. + /// + public class ContentModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext.ActionContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out var source) == false) + { + return Task.CompletedTask; + } + + // This model binder deals with IContentModel and IPublishedContent by extracting the model from the route's + // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler + // and both always set the model to an instance of `ContentModel`. + + // No need for type checks to ensure we have the appropriate binder, as in .NET Core this is handled in the provider, + // in this case ContentModelBinderProvider. + + // Being defensice though.... if for any reason the model is not either IContentModel or IPublishedContent, + // then we return since those are the only types this binder is dealing with. + if (source is IContentModel == false && source is IPublishedContent == false) + { + return Task.CompletedTask; + } + + BindModelAsync(bindingContext, source, bindingContext.ModelType); + return Task.CompletedTask; + } + + // source is the model that we have + // modelType is the type of the model that we need to bind to + // + // create a model object of the modelType by mapping: + // { ContentModel, ContentModel, IPublishedContent } + // to + // { ContentModel, ContentModel, IPublishedContent } + // + public Task BindModelAsync(ModelBindingContext bindingContext, object source, Type modelType) + { + // Null model, return + if (source == null) + { + return Task.CompletedTask; + } + + // If types already match, return + var sourceType = source.GetType(); + if (sourceType.Inherits(modelType)) // includes == + { + return Task.CompletedTask; + } + + // Try to grab the content + var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent + if (sourceContent == null && sourceType.Implements()) + // else check if it's an IContentModel, and get the content + sourceContent = ((IContentModel)source).Content; + if (sourceContent == null) + { + // else check if we can convert it to a content + var attempt1 = source.TryConvertTo(); + if (attempt1.Success) sourceContent = attempt1.Result; + } + + // If we have a content + if (sourceContent != null) + { + // If model is IPublishedContent, check content type and return + if (modelType.Implements()) + { + if (sourceContent.GetType().Inherits(modelType) == false) + { + ThrowModelBindingException(true, false, sourceContent.GetType(), modelType); + } + + bindingContext.Result = ModelBindingResult.Success(sourceContent); + return Task.CompletedTask; + } + + // If model is ContentModel, create and return + if (modelType == typeof(ContentModel)) + { + bindingContext.Result = ModelBindingResult.Success(new ContentModel(sourceContent)); + return Task.CompletedTask; + } + + // If model is ContentModel, check content type, then create and return + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) + { + var targetContentType = modelType.GetGenericArguments()[0]; + if (sourceContent.GetType().Inherits(targetContentType) == false) + { + ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType); + } + + bindingContext.Result = ModelBindingResult.Success(Activator.CreateInstance(modelType, sourceContent)); + return Task.CompletedTask; + } + } + + // Last chance : try to convert + var attempt2 = source.TryConvertTo(modelType); + if (attempt2.Success) + { + bindingContext.Result = ModelBindingResult.Success(attempt2.Result); + return Task.CompletedTask; + } + + // Fail + ThrowModelBindingException(false, false, sourceType, modelType); + return Task.CompletedTask; + } + + private void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType) + { + var msg = new StringBuilder(); + + // prepare message + msg.Append("Cannot bind source"); + if (sourceContent) msg.Append(" content"); + msg.Append(" type "); + msg.Append(sourceType.FullName); + msg.Append(" to model"); + if (modelContent) msg.Append(" content"); + msg.Append(" type "); + msg.Append(modelType.FullName); + msg.Append("."); + + // raise event, to give model factories a chance at reporting + // the error with more details, and optionally request that + // the application restarts. + + var args = new ModelBindingArgs(sourceType, modelType, msg); + ModelBindingException?.Invoke(this, args); + + throw new ModelBindingException(msg.ToString()); + } + + /// + /// Contains event data for the event. + /// + public class ModelBindingArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + public ModelBindingArgs(Type sourceType, Type modelType, StringBuilder message) + { + SourceType = sourceType; + ModelType = modelType; + Message = message; + } + + /// + /// Gets the type of the source object. + /// + public Type SourceType { get; set; } + + /// + /// Gets the type of the view model. + /// + public Type ModelType { get; set; } + + /// + /// Gets the message string builder. + /// + /// Handlers of the event can append text to the message. + public StringBuilder Message { get; } + + /// + /// Gets or sets a value indicating whether the application should restart. + /// + public bool Restart { get; set; } + } + + /// + /// Occurs on model binding exceptions. + /// + public static event EventHandler ModelBindingException; + } +} diff --git a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinderProvider.cs b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinderProvider.cs new file mode 100644 index 0000000000..9ce38abe03 --- /dev/null +++ b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinderProvider.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Common.ModelBinders +{ + /// + /// The provider for mapping view models, supporting mapping to and from any IPublishedContent or IContentModel. + /// + public class ContentModelBinderProvider : IModelBinderProvider + { + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + var modelType = context.Metadata.ModelType; + + // Can bind to ContentModel (exact type match) + // or to ContentModel (exact generic type match) + // or to TContent where TContent : IPublishedContent (any IPublishedContent implementation) + if (modelType == typeof(ContentModel) || + (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) || + typeof(IPublishedContent).IsAssignableFrom(modelType)) + { + return new BinderTypeModelBinder(typeof(ContentModelBinder)); + } + + return null; + } + } +} diff --git a/src/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinder.cs b/src/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinder.cs new file mode 100644 index 0000000000..eb6a1ab7fb --- /dev/null +++ b/src/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinder.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Primitives; +using Umbraco.Core; +using Umbraco.Web.Common.Extensions; + +namespace Umbraco.Web.Common.ModelBinders +{ + /// + /// Allows an Action to execute with an arbitrary number of QueryStrings + /// + /// + /// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number + /// but this will allow you to do it. + /// + public sealed class HttpQueryStringModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var queryStrings = GetQueryAsDictionary(bindingContext.ActionContext.HttpContext.Request.Query); + var queryStringKeys = queryStrings.Select(kvp => kvp.Key).ToArray(); + if (queryStringKeys.InvariantContains("culture") == false) + { + queryStrings.Add("culture", new StringValues(bindingContext.ActionContext.HttpContext.Request.ClientCulture())); + } + + var formData = new FormCollection(queryStrings); + bindingContext.Result = ModelBindingResult.Success(formData); + return Task.CompletedTask; + } + + private Dictionary GetQueryAsDictionary(IQueryCollection query) + { + var result = new Dictionary(); + if (query == null) + { + return result; + } + + foreach (var item in query) + { + result.Add(item.Key, item.Value); + } + + return result; + } + } +} diff --git a/src/Umbraco.Web.Common/ModelBinders/ModelBindingException.cs b/src/Umbraco.Web.Common/ModelBinders/ModelBindingException.cs new file mode 100644 index 0000000000..66ad642412 --- /dev/null +++ b/src/Umbraco.Web.Common/ModelBinders/ModelBindingException.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Common.ModelBinders +{ + /// + /// The exception that is thrown when an error occurs while binding a source to a model. + /// + /// + /// Migrated to .NET Core + [Serializable] + public class ModelBindingException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ModelBindingException() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public ModelBindingException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. + public ModelBindingException(string message, Exception innerException) + : base(message, innerException) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected ModelBindingException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 67a47a0a15..7b157179c3 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -3,10 +3,16 @@ module.exports = { compile: { build: { - sourcemaps: false + sourcemaps: false, + embedtemplates: true }, dev: { - sourcemaps: true + sourcemaps: true, + embedtemplates: true + }, + test: { + sourcemaps: false, + embedtemplates: true } }, sources: { @@ -17,7 +23,7 @@ module.exports = { installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" }, nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"}, preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" }, - umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" }, + umbraco: { files: "./src/less/belle.less", watch: "./src/**/*.less", out: "umbraco.css" }, rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" } }, diff --git a/src/Umbraco.Web.UI.Client/gulp/modes.js b/src/Umbraco.Web.UI.Client/gulp/modes.js index dc2947f2cc..21609cdcf8 100644 --- a/src/Umbraco.Web.UI.Client/gulp/modes.js +++ b/src/Umbraco.Web.UI.Client/gulp/modes.js @@ -10,4 +10,14 @@ function setDevelopmentMode(cb) { return cb(); }; -module.exports = { setDevelopmentMode: setDevelopmentMode }; +function setTestMode(cb) { + + config.compile.current = config.compile.test; + + return cb(); +}; + +module.exports = { + setDevelopmentMode: setDevelopmentMode, + setTestMode: setTestMode + }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js index 1e8d074f7e..255fe17435 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/test.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/test.js @@ -6,11 +6,24 @@ var karmaServer = require('karma').Server; * Build tests **************************/ - // Karma test +// Karma test function testUnit() { + return new karmaServer({ + configFile: __dirname + "/../../test/config/karma.conf.js" + }) + .start(); +}; + +// Run karma test server +function runUnitTestServer() { + return new karmaServer({ configFile: __dirname + "/../../test/config/karma.conf.js", + autoWatch: true, + port: 9999, + singleRun: false, + browsers: ['ChromeDebugging'], keepalive: true }) .start(); @@ -24,4 +37,4 @@ function testE2e() { .start(); }; -module.exports = { testUnit: testUnit, testE2e: testE2e }; +module.exports = { testUnit: testUnit, testE2e: testE2e, runUnitTestServer: runUnitTestServer }; diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index db24dae23e..744481391a 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -1,7 +1,7 @@ 'use strict'; const config = require('../config'); -const {watch, parallel, dest, src} = require('gulp'); +const {watch, series, parallel, dest, src} = require('gulp'); var _ = require('lodash'); var MergeStream = require('merge-stream'); @@ -9,9 +9,7 @@ var MergeStream = require('merge-stream'); var processJs = require('../util/processJs'); var processLess = require('../util/processLess'); -//const { less } = require('./less'); -//const { views } = require('./views'); - +var {js} = require('./js'); function watchTask(cb) { @@ -30,24 +28,27 @@ function watchTask(cb) { watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { return processLess(group.files, group.out); }); } }); - + //Setup a watcher for all groups of view files var viewWatcher; _.forEach(config.sources.views, function (group) { if(group.watch !== false) { - viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }); - viewWatcher.on('change', function(path, stats) { - - var task = src(group.files); + viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }, + parallel( + function MoveViewsAndRegenerateJS() { + var task = src(group.files); - _.forEach(config.roots, function(root){ - console.log("copying " + group.files + " to " + root + config.targets.views + group.folder); - task = task.pipe( dest(root + config.targets.views + group.folder) ); - }) - }); + _.forEach(config.roots, function(root){ + console.log("copying " + group.files + " to " + root + config.targets.views + group.folder); + task = task.pipe( dest(root + config.targets.views + group.folder) ); + }); + }, + js + ) + ); } }); - + return cb(); }; diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js index 9ff9a93c09..6c6f1276e7 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processJs.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processJs.js @@ -28,7 +28,9 @@ module.exports = function (files, out) { .pipe(sort()); //in production, embed the templates - task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })) + if(config.compile.current.embedtemplates === true) { + task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } })); + } task = task.pipe(concat(out)).pipe(wrap('(function(){\n%= body %\n})();')) diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 705c54bf04..542d45c479 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -13,11 +13,11 @@ const { src, dest, series, parallel, lastRun } = require('gulp'); const config = require('./gulp/config'); -const { setDevelopmentMode } = require('./gulp/modes'); +const { setDevelopmentMode, setTestMode } = require('./gulp/modes'); const { dependencies } = require('./gulp/tasks/dependencies'); const { js } = require('./gulp/tasks/js'); const { less } = require('./gulp/tasks/less'); -const { testE2e, testUnit } = require('./gulp/tasks/test'); +const { testE2e, testUnit, runUnitTestServer } = require('./gulp/tasks/test'); const { views } = require('./gulp/tasks/views'); const { watchTask } = require('./gulp/tasks/watchTask'); @@ -31,6 +31,6 @@ exports.build = series(parallel(dependencies, js, less, views), testUnit); exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask); exports.watch = series(watchTask); // -exports.runTests = series(js, testUnit); -exports.testUnit = series(testUnit); -exports.testE2e = series(testE2e); +exports.runTests = series(setTestMode, parallel(js, testUnit)); +exports.runUnit = series(setTestMode, parallel(js, runUnitTestServer), watchTask); +exports.testE2e = series(setTestMode, parallel(testE2e)); diff --git a/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js new file mode 100644 index 0000000000..90eae85800 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/tinymce/langs/en_US.js @@ -0,0 +1,261 @@ +tinymce.addI18n('en_US',{ +"Redo": "Redo", +"Undo": "Undo", +"Cut": "Cut", +"Copy": "Copy", +"Paste": "Paste", +"Select all": "Select all", +"New document": "New document", +"Ok": "Ok", +"Cancel": "Cancel", +"Visual aids": "Visual aids", +"Bold": "Bold", +"Italic": "Italic", +"Underline": "Underline", +"Strikethrough": "Strikethrough", +"Superscript": "Superscript", +"Subscript": "Subscript", +"Clear formatting": "Clear formatting", +"Align left": "Align left", +"Align center": "Align center", +"Align right": "Align right", +"Justify": "Justify", +"Bullet list": "Bullet list", +"Numbered list": "Numbered list", +"Decrease indent": "Decrease indent", +"Increase indent": "Increase indent", +"Close": "Close", +"Formats": "Formats", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.", +"Headers": "Headers", +"Header 1": "Header 1", +"Header 2": "Header 2", +"Header 3": "Header 3", +"Header 4": "Header 4", +"Header 5": "Header 5", +"Header 6": "Header 6", +"Headings": "Headings", +"Heading 1": "Heading 1", +"Heading 2": "Heading 2", +"Heading 3": "Heading 3", +"Heading 4": "Heading 4", +"Heading 5": "Heading 5", +"Heading 6": "Heading 6", +"Preformatted": "Preformatted", +"Div": "Div", +"Pre": "Pre", +"Code": "Code", +"Paragraph": "Paragraph", +"Blockquote": "Blockquote", +"Inline": "Inline", +"Blocks": "Blocks", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.", +"Font Family": "Font Family", +"Font Sizes": "Font Sizes", +"Class": "Class", +"Browse for an image": "Browse for an image", +"OR": "OR", +"Drop an image here": "Drop an image here", +"Upload": "Upload", +"Block": "Blocks", +"Align": "Align", +"Default": "Default", +"Circle": "Circle", +"Disc": "Disc", +"Square": "Square", +"Lower Alpha": "Lower Alpha", +"Lower Greek": "Lower Greek", +"Lower Roman": "Lower Roman", +"Upper Alpha": "Upper Alpha", +"Upper Roman": "Upper Roman", +"Anchor": "Anchor", +"Name": "Name", +"Id": "ID", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons, or underscores.", +"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?", +"Restore last draft": "Restore last draft", +"Special character": "Special character", +"Source code": "Source code", +"Insert\/Edit code sample": "Insert\/Edit code sample", +"Language": "Language", +"Code sample": "Code sample", +"Color": "color", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "Left to right", +"Right to left": "Right to left", +"Emoticons": "Emoticons", +"Document properties": "Document properties", +"Title": "Title", +"Keywords": "Keywords", +"Description": "Description", +"Robots": "Robots", +"Author": "Author", +"Encoding": "Encoding", +"Fullscreen": "Fullscreen", +"Action": "Action", +"Shortcut": "Shortcut", +"Help": "Help", +"Address": "Address", +"Focus to menubar": "Focus to menubar", +"Focus to toolbar": "Focus to toolbar", +"Focus to element path": "Focus to element path", +"Focus to contextual toolbar": "Focus to contextual toolbar", +"Insert link (if link plugin activated)": "Insert link (if link plugin activated)", +"Save (if save plugin activated)": "Save (if save plugin activated)", +"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)", +"Plugins installed ({0}):": "Plugins installed ({0}):", +"Premium plugins:": "Premium plugins:", +"Learn more...": "Learn more...", +"You are using {0}": "You are using {0}", +"Plugins": "Plugins", +"Handy Shortcuts": "Handy Shortcuts", +"Horizontal line": "Horizontal line", +"Insert\/edit image": "Insert\/edit image", +"Image description": "Image description", +"Source": "Source", +"Dimensions": "Dimensions", +"Constrain proportions": "Constrain proportions", +"General": "General", +"Advanced": "Advanced", +"Style": "Style", +"Vertical space": "Vertical space", +"Horizontal space": "Horizontal space", +"Border": "Border", +"Insert image": "Insert image", +"Image": "Image", +"Image list": "Image list", +"Rotate counterclockwise": "Rotate counterclockwise", +"Rotate clockwise": "Rotate clockwise", +"Flip vertically": "Flip vertically", +"Flip horizontally": "Flip horizontally", +"Edit image": "Edit image", +"Image options": "Image options", +"Zoom in": "Zoom in", +"Zoom out": "Zoom out", +"Crop": "Crop", +"Resize": "Resize", +"Orientation": "Orientation", +"Brightness": "Brightness", +"Sharpen": "Sharpen", +"Contrast": "Contrast", +"Color levels": "color levels", +"Gamma": "Gamma", +"Invert": "Invert", +"Apply": "Apply", +"Back": "Back", +"Insert date\/time": "Insert date\/time", +"Date\/time": "Date\/time", +"Insert link": "Insert link", +"Insert\/edit link": "Insert\/edit link", +"Text to display": "Text to display", +"Url": "Url", +"Target": "Target", +"None": "None", +"New window": "New window", +"Remove link": "Remove link", +"Anchors": "Anchors", +"Link": "Link", +"Paste or type a link": "Paste or type a link", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?", +"Link list": "Link list", +"Insert video": "Insert video", +"Insert\/edit video": "Insert\/edit video", +"Insert\/edit media": "Insert\/edit media", +"Alternative source": "Alternative source", +"Poster": "Poster", +"Paste your embed code below:": "Paste your embed code below:", +"Embed": "Embed", +"Media": "Media", +"Nonbreaking space": "Nonbreaking space", +"Page break": "Page break", +"Paste as text": "Paste as text", +"Preview": "Preview", +"Print": "Print", +"Save": "Save", +"Find": "Find", +"Replace with": "Replace with", +"Replace": "Replace", +"Replace all": "Replace all", +"Prev": "Prev", +"Next": "Next", +"Find and replace": "Find and replace", +"Could not find the specified string.": "Could not find the specified string.", +"Match case": "Match case", +"Whole words": "Whole words", +"Spellcheck": "Spellcheck", +"Ignore": "Ignore", +"Ignore all": "Ignore all", +"Finish": "Finish", +"Add to Dictionary": "Add to Dictionary", +"Insert table": "Insert table", +"Table properties": "Table properties", +"Delete table": "Delete table", +"Cell": "Cell", +"Row": "Row", +"Column": "Column", +"Cell properties": "Cell properties", +"Merge cells": "Merge cells", +"Split cell": "Split cell", +"Insert row before": "Insert row before", +"Insert row after": "Insert row after", +"Delete row": "Delete row", +"Row properties": "Row properties", +"Cut row": "Cut row", +"Copy row": "Copy row", +"Paste row before": "Paste row before", +"Paste row after": "Paste row after", +"Insert column before": "Insert column before", +"Insert column after": "Insert column after", +"Delete column": "Delete column", +"Cols": "Cols", +"Rows": "Rows", +"Width": "Width", +"Height": "Height", +"Cell spacing": "Cell spacing", +"Cell padding": "Cell padding", +"Caption": "Caption", +"Left": "Left", +"Center": "Center", +"Right": "Right", +"Cell type": "Cell type", +"Scope": "Scope", +"Alignment": "Alignment", +"H Align": "H Align", +"V Align": "V Align", +"Top": "Top", +"Middle": "Middle", +"Bottom": "Bottom", +"Header cell": "Header cell", +"Row group": "Row group", +"Column group": "Column group", +"Row type": "Row type", +"Header": "Header", +"Body": "Body", +"Footer": "Footer", +"Border color": "Border color", +"Insert template": "Insert template", +"Templates": "Templates", +"Template": "Template", +"Text color": "Text color", +"Background color": "Background color", +"Custom...": "Custom...", +"Custom color": "Custom color", +"No color": "No color", +"Table of Contents": "Table of Contents", +"Show blocks": "Show blocks", +"Show invisible characters": "Show invisible characters", +"Words: {0}": "Words: {0}", +"{0} words": "{0} words", +"File": "File", +"Edit": "Edit", +"Insert": "Insert", +"View": "View", +"Format": "Format", +"Table": "Table", +"Tools": "Tools", +"Powered by {0}": "Powered by {0}", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help" +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json deleted file mode 100644 index b8087066c9..0000000000 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ /dev/null @@ -1,16217 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/core": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", - "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.4", - "@babel/helpers": "^7.6.2", - "@babel/parser": "^7.6.4", - "@babel/template": "^7.6.0", - "@babel/traverse": "^7.6.3", - "@babel/types": "^7.6.3", - "convert-source-map": "^1.1.0", - "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", - "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", - "dev": true, - "requires": { - "@babel/types": "^7.6.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/parser": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", - "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", - "dev": true - }, - "@babel/traverse": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", - "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.3", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.3", - "@babel/types": "^7.6.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", - "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", - "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", - "dev": true, - "requires": { - "@babel/types": "^7.6.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", - "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", - "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-call-delegate": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", - "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" - } - }, - "@babel/helper-define-map": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", - "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.5.5", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", - "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", - "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", - "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", - "dev": true, - "requires": { - "@babel/types": "^7.5.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-module-transforms": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", - "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/template": "^7.4.4", - "@babel/types": "^7.5.5", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", - "dev": true - }, - "@babel/helper-regex": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", - "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", - "dev": true, - "requires": { - "lodash": "^4.17.13" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", - "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-wrap-function": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-replace-supers": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", - "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.5.5", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", - "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", - "dev": true, - "requires": { - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4" - } - }, - "@babel/helper-wrap-function": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", - "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.2.0" - } - }, - "@babel/helpers": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", - "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", - "dev": true, - "requires": { - "@babel/template": "^7.6.0", - "@babel/traverse": "^7.6.2", - "@babel/types": "^7.6.0" - } - }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", - "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", - "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0", - "@babel/plugin-syntax-async-generators": "^7.2.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", - "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", - "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.2.0" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", - "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", - "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", - "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", - "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", - "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", - "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", - "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", - "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", - "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz", - "integrity": "sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", - "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.5.5", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.5.5", - "@babel/helper-split-export-declaration": "^7.4.4", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", - "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", - "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", - "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", - "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", - "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", - "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", - "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", - "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", - "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", - "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", - "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", - "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", - "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz", - "integrity": "sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==", - "dev": true, - "requires": { - "regexpu-core": "^4.6.0" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", - "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", - "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.5.5" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", - "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", - "dev": true, - "requires": { - "@babel/helper-call-delegate": "^7.4.4", - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", - "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", - "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", - "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", - "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", - "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", - "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", - "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", - "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", - "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" - } - }, - "@babel/preset-env": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.3.tgz", - "integrity": "sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-dynamic-import": "^7.5.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.6.3", - "@babel/plugin-transform-classes": "^7.5.5", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.6.0", - "@babel/plugin-transform-dotall-regex": "^7.6.2", - "@babel/plugin-transform-duplicate-keys": "^7.5.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.4", - "@babel/plugin-transform-function-name": "^7.4.4", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.6.0", - "@babel/plugin-transform-modules-systemjs": "^7.5.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.3", - "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.5.5", - "@babel/plugin-transform-parameters": "^7.4.4", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.5", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.6.2", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.4.4", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.6.2", - "@babel/types": "^7.6.3", - "browserslist": "^4.6.0", - "core-js-compat": "^3.1.1", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.5.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", - "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/template": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", - "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0" - } - }, - "@babel/traverse": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", - "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.2", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.2", - "@babel/types": "^7.6.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "@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.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "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", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.3", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.3", - "fastq": "^1.6.0" - } - }, - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true, - "optional": true - }, - "@types/angular": { - "version": "1.6.56", - "resolved": "https://registry.npmjs.org/@types/angular/-/angular-1.6.56.tgz", - "integrity": "sha512-HxtqilvklZ7i6XOaiP7uIJIrFXEVEhfbSY45nfv2DeBRngncI58Y4ZOUMiUkcT8sqgLL1ablmbfylChUg7A3GA==" - }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true - }, - "@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/node": { - "version": "12.11.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.2.tgz", - "integrity": "sha512-dsfE4BHJkLQW+reOS6b17xhZ/6FB1rB8eRRvO08nn5o+voxf3i74tuyFWNH6djdfgX7Sm5s6LD8t6mJug4dpDw==", - "dev": true - }, - "@types/q": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", - "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "accord": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", - "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", - "dev": true, - "requires": { - "convert-source-map": "^1.5.0", - "glob": "^7.0.5", - "indx": "^0.2.3", - "lodash.clone": "^4.3.2", - "lodash.defaults": "^4.0.1", - "lodash.flatten": "^4.2.0", - "lodash.merge": "^4.4.0", - "lodash.partialright": "^4.1.4", - "lodash.pick": "^4.2.1", - "lodash.uniq": "^4.3.0", - "resolve": "^1.5.0", - "semver": "^5.3.0", - "uglify-js": "^2.8.22", - "when": "^3.7.8" - }, - "dependencies": { - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "ace-builds": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.2.tgz", - "integrity": "sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA==" - }, - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true - }, - "acorn-jsx": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", - "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", - "dev": true - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "angular": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.9.tgz", - "integrity": "sha512-5se7ZpcOtu0MBFlzGv5dsM1quQDoDeUTwZrWjGtTNA7O88cD8TEk5IEKCTDa3uECV9XnvKREVUr7du1ACiWGFQ==" - }, - "angular-animate": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", - "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" - }, - "angular-aria": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", - "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" - }, - "angular-chart.js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz", - "integrity": "sha1-SfDhjQgXYrbUyXkeSHr/L7sw9a4=", - "requires": { - "angular": "1.x", - "chart.js": "2.3.x" - }, - "dependencies": { - "chart.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.3.0.tgz", - "integrity": "sha1-QEYOSOLEF8BfwzJc2E97AA3H19Y=", - "requires": { - "chartjs-color": "^2.0.0", - "moment": "^2.10.6" - } - } - } - }, - "angular-cookies": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", - "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" - }, - "angular-dynamic-locale": { - "version": "0.1.37", - "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", - "integrity": "sha512-m5Kyk8W8/mOZSqRxuByOwHBjv8labLBAgvl0Z3iQx2xT/tWCqb94imKUPwumudszdPDjxeopwyucQvm8Sw7ogw==", - "requires": { - "@types/angular": "^1.6.25" - } - }, - "angular-i18n": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", - "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" - }, - "angular-local-storage": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/angular-local-storage/-/angular-local-storage-0.7.1.tgz", - "integrity": "sha1-+9JzB2PCn6mvVyXgGGx4BiHozdI=" - }, - "angular-messages": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", - "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" - }, - "angular-mocks": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", - "integrity": "sha512-I+Ue2Bkx6R9W5178DYrNvzjIdGh4wKKoCWsgz8dc7ysH4mA70Q3M9v5xRF0RUu7r+2CZj+nDeUecvh2paxcYvg==" - }, - "angular-route": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", - "integrity": "sha512-7KfyEVVOWTI+jTY/j5rUNCIHGRyeCOx7YqZI/Ci3IbDK7GIsy6xH+hS5ai0Xi0sLjzDZ0PUDO4gBn+K0dVtlOg==" - }, - "angular-sanitize": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", - "integrity": "sha512-wjKCJOIwrkEvfD0keTnKGi6We13gtoCAQIHcdoqyoo3gwvcgNfYymVQIS3+iCGVcjfWz0jHuS3KgB4ysRWsTTA==" - }, - "angular-touch": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", - "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" - }, - "angular-ui-sortable": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.19.0.tgz", - "integrity": "sha512-u/uc981Nzg4XN1bMU9qKleMTSt7F1XjMWnyGw6gxPLIeQeLZm8jWNy7tj8y2r2HmvzXFbQVq2z6rObznFKAekQ==", - "requires": { - "angular": ">=1.2.x", - "jquery": ">=3.1.x", - "jquery-ui-dist": ">=1.12.x" - } - }, - "animejs": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/animejs/-/animejs-2.2.0.tgz", - "integrity": "sha1-Ne79/FNbgZScnLBvCz5gwC5v3IA=" - }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", - "dev": true, - "requires": { - "ansi-wrap": "0.1.0" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - }, - "dependencies": { - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - } - } - }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", - "dev": true, - "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - } - } - }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", - "dev": true, - "requires": { - "buffer-equal": "^1.0.0" - } - }, - "arch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", - "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==", - "dev": true, - "optional": true - }, - "archive-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", - "integrity": "sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA=", - "dev": true, - "optional": true, - "requires": { - "file-type": "^4.2.0" - }, - "dependencies": { - "file-type": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", - "integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU=", - "dev": true, - "optional": true - } - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "argh": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/argh/-/argh-0.1.4.tgz", - "integrity": "sha1-PrTWEpc/xrbcbvM49W91nyrFw6Y=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", - "dev": true - }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" - } - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true, - "optional": true - }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", - "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", - "dev": true - }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true, - "optional": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", - "dev": true, - "requires": { - "async-done": "^1.2.2" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "autoprefixer": { - "version": "9.6.5", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.5.tgz", - "integrity": "sha512-rGd50YV8LgwFQ2WQp4XzOTG69u1qQsXn0amww7tjqV5jJuNazgFKYEVItEBngyyvVITKOg20zr2V+9VsrXJQ2g==", - "dev": true, - "requires": { - "browserslist": "^4.7.0", - "caniuse-lite": "^1.0.30000999", - "chalk": "^2.4.2", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.18", - "postcss-value-parser": "^4.0.2" - }, - "dependencies": { - "postcss-value-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", - "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", - "dev": true - } - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", - "dev": true, - "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - } - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true, - "optional": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "requires": { - "callsite": "1.0.0" - } - }, - "bin-build": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", - "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", - "dev": true, - "optional": true, - "requires": { - "decompress": "^4.0.0", - "download": "^6.2.2", - "execa": "^0.7.0", - "p-map-series": "^1.0.0", - "tempfile": "^2.0.0" - } - }, - "bin-check": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", - "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", - "dev": true, - "optional": true, - "requires": { - "execa": "^0.7.0", - "executable": "^4.1.0" - } - }, - "bin-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", - "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", - "dev": true, - "optional": true, - "requires": { - "execa": "^1.0.0", - "find-versions": "^3.0.0" - }, - "dependencies": { - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "optional": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "optional": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "bin-version-check": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", - "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", - "dev": true, - "optional": true, - "requires": { - "bin-version": "^3.0.0", - "semver": "^5.6.0", - "semver-truncate": "^1.1.2" - } - }, - "bin-wrapper": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", - "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", - "dev": true, - "optional": true, - "requires": { - "bin-check": "^4.1.0", - "bin-version-check": "^4.0.0", - "download": "^7.1.0", - "import-lazy": "^3.1.0", - "os-filter-obj": "^2.0.0", - "pify": "^4.0.1" - }, - "dependencies": { - "download": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", - "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", - "dev": true, - "optional": true, - "requires": { - "archive-type": "^4.0.0", - "caw": "^2.0.1", - "content-disposition": "^0.5.2", - "decompress": "^4.2.0", - "ext-name": "^5.0.0", - "file-type": "^8.1.0", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^8.3.1", - "make-dir": "^1.2.0", - "p-event": "^2.1.0", - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - }, - "file-type": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", - "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", - "dev": true, - "optional": true - }, - "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", - "dev": true, - "optional": true, - "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "optional": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", - "dev": true, - "optional": true - }, - "p-event": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", - "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", - "dev": true, - "optional": true, - "requires": { - "p-timeout": "^2.0.1" - } - }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "dev": true, - "optional": true, - "requires": { - "p-finally": "^1.0.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "optional": true - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "optional": true - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "optional": true, - "requires": { - "prepend-http": "^2.0.0" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "bl": { - "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "dev": true, - "optional": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, - "bluebird": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", - "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", - "dev": true - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - } - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "bootstrap": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", - "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==" - }, - "bootstrap-social": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bootstrap-social/-/bootstrap-social-5.1.1.tgz", - "integrity": "sha1-dTDGeK31bPj60/qCwp1NPl0CdQE=", - "requires": { - "bootstrap": "~3", - "font-awesome": "~4.7" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "browserslist": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", - "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000989", - "electron-to-chromium": "^1.3.247", - "node-releases": "^1.1.29" - } - }, - "buffer": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", - "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", - "dev": true, - "optional": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", - "dev": true - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "optional": true - }, - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "bufferstreams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", - "integrity": "sha1-z7GtlWjTujz+k1upq92VLeiKqyo=", - "dev": true, - "requires": { - "readable-stream": "^1.0.33" - } - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", - "dev": true, - "optional": true, - "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true, - "optional": true - }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "optional": true, - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - } - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "optional": true - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "optional": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - } - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "dev": true, - "requires": { - "callsites": "^2.0.0" - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true, - "optional": true - } - } - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001040", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz", - "integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "caw": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", - "dev": true, - "optional": true, - "requires": { - "get-proxy": "^2.0.0", - "isurl": "^1.0.0-alpha5", - "tunnel-agent": "^0.6.0", - "url-to-options": "^1.0.1" - } - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "chart.js": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz", - "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==", - "requires": { - "chartjs-color": "^2.1.0", - "moment": "^2.10.2" - } - }, - "chartjs-color": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.3.0.tgz", - "integrity": "sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==", - "requires": { - "chartjs-color-string": "^0.6.0", - "color-convert": "^0.5.3" - } - }, - "chartjs-color-string": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", - "requires": { - "color-name": "^1.0.0" - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - } - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", - "dev": true, - "requires": { - "source-map": "~0.6.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 - } - } - }, - "cli-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", - "dev": true, - "requires": { - "ansi-regex": "^2.1.1", - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.5" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", - "requires": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "optional": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "dev": true, - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", - "dev": true, - "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - }, - "dependencies": { - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - } - } - }, - "color-convert": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", - "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "dev": true, - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", - "dev": true - }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "dev": true, - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" - }, - "dependencies": { - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - } - } - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", - "dev": true, - "optional": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha1-1OqT8FriV5CVG5nns7CeOQikCC4=", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", - "dev": true - } - } - }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", - "dev": true, - "optional": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "console-stream": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/console-stream/-/console-stream-0.1.1.tgz", - "integrity": "sha1-oJX+B7IEZZVfL6/Si11yvM2UnUQ=", - "dev": true, - "optional": true - }, - "consolidate": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "dev": true, - "requires": { - "bluebird": "^3.1.1" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", - "dev": true - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", - "dev": true, - "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" - } - }, - "core-js-compat": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.3.3.tgz", - "integrity": "sha512-GNZkENsx5pMnS7Inwv7ZO/s3B68a9WU5kIjxqrD/tkNR8mtfXJRk8fAKRlbvWZSGPc59/TkiOBDYl5Cb65pTVA==", - "dev": true, - "requires": { - "browserslist": "^4.7.1", - "semver": "^6.3.0" - }, - "dependencies": { - "browserslist": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.1.tgz", - "integrity": "sha512-QtULFqKIAtiyNx7NhZ/p4rB8m3xDozVo/pi5VgTlADLF2tNigz/QH+v0m5qhn7XfHT7u+607NcCNOnC0HZAlMg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000999", - "electron-to-chromium": "^1.3.284", - "node-releases": "^1.1.36" - } - }, - "electron-to-chromium": { - "version": "1.3.292", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.292.tgz", - "integrity": "sha512-hqkem5ANpt6mxVXmhAmlbdG8iicuyM/jEYgmP1tiHPeOLyZoTyGUzrDmJS/xyrrZy9frkW1uQcubicu7f6DS5g==", - "dev": true - }, - "node-releases": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.37.tgz", - "integrity": "sha512-0EOsAEdn6S2vQdDGBWBpmClm5BCkXVkVOURdnhfg7//rxI2XbleRdKig87WuBrk+0PHZ4OhO58fRm9mzWW4jNw==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "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", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "dev": true, - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - } - }, - "css-select": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", - "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^2.1.2", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", - "dev": true - }, - "css-tree": { - "version": "1.0.0-alpha.33", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.33.tgz", - "integrity": "sha512-SPt57bh5nQnpsTBsx/IXbO14sRc9xXu5MtMAVuo0BaQQmyf0NupNPPSoMaqiAF5tDFafYsTkfeH4Q/HCKXkg4w==", - "dev": true, - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.5.3" - } - }, - "css-unit-converter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", - "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", - "dev": true - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", - "dev": true - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "dev": true, - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", - "dev": true - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", - "dev": true - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "dev": true - }, - "csso": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", - "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", - "dev": true, - "requires": { - "css-tree": "1.0.0-alpha.29" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.29", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", - "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", - "dev": true, - "requires": { - "mdn-data": "~1.1.0", - "source-map": "^0.5.3" - } - }, - "mdn-data": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", - "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", - "dev": true - } - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "optional": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "dev": true - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "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", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "decompress": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", - "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", - "dev": true, - "optional": true, - "requires": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" - }, - "dependencies": { - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "optional": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - } - } - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "optional": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "decompress-tar": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", - "dev": true, - "optional": true, - "requires": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" - }, - "dependencies": { - "file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true, - "optional": true - } - } - }, - "decompress-tarbz2": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", - "dev": true, - "optional": true, - "requires": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" - }, - "dependencies": { - "file-type": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true, - "optional": true - } - } - }, - "decompress-targz": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", - "dev": true, - "optional": true, - "requires": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" - }, - "dependencies": { - "file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true, - "optional": true - } - } - }, - "decompress-unzip": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", - "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", - "dev": true, - "optional": true, - "requires": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" - }, - "dependencies": { - "file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true, - "optional": true - }, - "get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", - "dev": true, - "optional": true, - "requires": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - } - }, - "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, - "optional": true - } - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "requires": { - "kind-of": "^5.0.2" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "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", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "dev": true, - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "dom-serializer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", - "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", - "dev": true - } - } - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true - }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "download": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", - "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", - "dev": true, - "optional": true, - "requires": { - "caw": "^2.0.0", - "content-disposition": "^0.5.2", - "decompress": "^4.0.0", - "ext-name": "^5.0.0", - "file-type": "5.2.0", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^7.0.0", - "make-dir": "^1.0.0", - "p-event": "^1.0.0", - "pify": "^3.0.0" - }, - "dependencies": { - "file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true, - "optional": true - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "optional": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true, - "optional": true - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.266", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.266.tgz", - "integrity": "sha512-UTuTZ4v8T0gLPHI7U75PXLQePWI65MTS3mckRrnLCkNljHvsutbYs+hn2Ua/RFul3Jt/L3Ht2rLP+dU/AlBfrQ==", - "dev": true - }, - "emits": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz", - "integrity": "sha1-MnUrupXhcHshlWI4Srm7ix/WL3A=", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "dev": true, - "requires": { - "env-variable": "0.0.x" - } - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - }, - "dependencies": { - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - } - } - }, - "engine.io": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "engine.io-client": { - "version": "3.2.1", - "resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "engine.io-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", - "dev": true, - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", - "dev": true - }, - "env-variable": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", - "dev": true, - "optional": true, - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.15.0.tgz", - "integrity": "sha512-bhkEqWJ2t2lMeaJDuk7okMkJWI/yqgH/EoGwpcvv0XW9RWQsRspI4wt6xuyuvMvvQE3gg/D9HXppgk21w78GyQ==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.0", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-inspect": "^1.6.0", - "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es5-ext": { - "version": "0.10.51", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", - "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-symbol": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", - "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", - "dev": true, - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.51" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "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, - "optional": true - } - } - }, - "eslint": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.5.1.tgz", - "integrity": "sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.2", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.4.1", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "eslint-scope": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", - "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", - "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", - "dev": true - }, - "espree": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", - "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", - "dev": true, - "requires": { - "acorn": "^7.1.0", - "acorn-jsx": "^5.1.0", - "eslint-visitor-keys": "^1.1.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estemplate": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/estemplate/-/estemplate-0.5.1.tgz", - "integrity": "sha1-FxSp1GGQc4rJWLyv1J4CnNpWo54=", - "dev": true, - "requires": { - "esprima": "^2.7.2", - "estraverse": "^4.1.1" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", - "dev": true - }, - "exec-buffer": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", - "dev": true, - "optional": true, - "requires": { - "execa": "^0.7.0", - "p-finally": "^1.0.0", - "pify": "^3.0.0", - "rimraf": "^2.5.4", - "tempfile": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "optional": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - } - } - }, - "executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "optional": true, - "requires": { - "pify": "^2.2.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "ext-list": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", - "dev": true, - "optional": true, - "requires": { - "mime-db": "^1.28.0" - } - }, - "ext-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", - "dev": true, - "optional": true, - "requires": { - "ext-list": "^2.0.0", - "sort-keys-length": "^1.0.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "~1.0.1" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", - "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", - "merge2": "^1.3.0", - "micromatch": "^4.0.2" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", - "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", - "dev": true, - "requires": { - "reusify": "^1.0.0" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "dev": true, - "optional": true, - "requires": { - "pend": "~1.2.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "file-type": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.3.1.tgz", - "integrity": "sha512-FXxY5h6vSYMjrRal4YqbtfuoKD/oE0AMjJ7E5Hm+BdaQECcFVD03B41RAWYJ7wyuLr/wRnCtFo7y37l+nh+TAA==", - "dev": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true, - "optional": true - }, - "filenamify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", - "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", - "dev": true, - "optional": true, - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.0", - "trim-repeated": "^1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "find-versions": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.1.0.tgz", - "integrity": "sha512-NCTfNiVzeE/xL+roNDffGuRbrWI6atI18lTJ22vKp7rs2OhYzMK3W1dIdO2TUndH/QMcacM4d1uWwgcZcHK69Q==", - "dev": true, - "optional": true, - "requires": { - "array-uniq": "^2.1.0", - "semver-regex": "^2.0.0" - }, - "dependencies": { - "array-uniq": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-2.1.0.tgz", - "integrity": "sha512-bdHxtev7FN6+MXI1YFW0Q8mQ8dTJc2S8AMfju+ZR77pbg2yAdVyDlwkaUI7Har0LyOMRFPHrJ9lYdyjZZswdlQ==", - "dev": true, - "optional": true - } - } - }, - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - } - }, - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatpickr": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", - "integrity": "sha512-jDy4QYGpmiy7+Qk8QvKJ4spjDdxcx9cxMydmq1x427HkKWBw0qizLYeYM2F6tMcvvqGjU5VpJS55j4LnsaBblA==" - }, - "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", - "dev": true - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", - "dev": true, - "requires": { - "debug": "^3.0.0" - }, - "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" - } - } - } - }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "optional": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.2.tgz", - "integrity": "sha1-4fJE7zkzwbKmS9R5kTYGDQ9ZFPg=", - "dev": true - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true, - "optional": true - }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true - } - } - }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - } - }, - "fs-readfile-promise": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", - "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^4.1.0", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-proxy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", - "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", - "dev": true, - "optional": true, - "requires": { - "npm-conf": "^1.1.0" - } - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true, - "optional": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "optional": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "gifsicle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gifsicle/-/gifsicle-4.0.1.tgz", - "integrity": "sha512-A/kiCLfDdV+ERV/UB+2O41mifd+RxH8jlRG8DMxZO84Bma/Fw0htqZ+hY2iaalLRNyUu7tYZQslqUBJxBggxbg==", - "dev": true, - "optional": true, - "requires": { - "bin-build": "^3.0.0", - "bin-wrapper": "^4.0.0", - "execa": "^1.0.0", - "logalot": "^2.0.0" - }, - "dependencies": { - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "optional": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "optional": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "object.defaults": "^1.1.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - } - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", - "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", - "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - } - } - }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "requires": { - "delegate": "^3.1.2" - } - }, - "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dev": true, - "optional": true, - "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - } - }, - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true, - "optional": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "requires": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - } - }, - "gulp-angular-embed-templates": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-angular-embed-templates/-/gulp-angular-embed-templates-2.3.0.tgz", - "integrity": "sha1-wBDv3VlN7pRRMoNFN9eSOt6EDXk=", - "dev": true, - "requires": { - "gulp-util": "^3.0.6", - "htmlparser2": "~3.9.1", - "minimize": "^2.0.0", - "object-assign": "4.1.0", - "through2": "^2.0.1" - }, - "dependencies": { - "object-assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", - "dev": true - } - } - }, - "gulp-babel": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", - "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", - "dev": true, - "requires": { - "plugin-error": "^1.0.1", - "replace-ext": "^1.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "dependencies": { - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - } - } - }, - "gulp-clean-css": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-4.2.0.tgz", - "integrity": "sha512-r4zQsSOAK2UYUL/ipkAVCTRg/2CLZ2A+oPVORopBximRksJ6qy3EX1KGrIWT4ZrHxz3Hlobb1yyJtqiut7DNjA==", - "dev": true, - "requires": { - "clean-css": "4.2.1", - "plugin-error": "1.0.1", - "through2": "3.0.1", - "vinyl-sourcemaps-apply": "0.2.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } - } - } - }, - "gulp-cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", - "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.1.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", - "yargs": "^7.1.0" - }, - "dependencies": { - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - } - }, - "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - } - } - }, - "gulp-concat": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", - "integrity": "sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M=", - "dev": true, - "requires": { - "concat-with-sourcemaps": "^1.0.0", - "through2": "^2.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "gulp-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", - "dev": true, - "requires": { - "eslint": "^6.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1" - } - }, - "gulp-imagemin": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gulp-imagemin/-/gulp-imagemin-6.1.1.tgz", - "integrity": "sha512-fqaSR8bMc5lhqa6HzRPuJaDY6lY7rcCipe6WtqQ5+hNYTCSPYjXic+1gvFG1+8X879gjVJmIxwmqIbfjuMqTpQ==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "fancy-log": "^1.3.2", - "imagemin": "^7.0.0", - "imagemin-gifsicle": "^6.0.1", - "imagemin-jpegtran": "^6.0.0", - "imagemin-optipng": "^7.0.0", - "imagemin-svgo": "^7.0.0", - "plugin-error": "^1.0.1", - "plur": "^3.0.1", - "pretty-bytes": "^5.3.0", - "through2-concurrent": "^2.0.0" - } - }, - "gulp-less": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", - "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", - "dev": true, - "requires": { - "accord": "^0.29.0", - "less": "2.6.x || ^3.7.1", - "object-assign": "^4.0.1", - "plugin-error": "^0.1.2", - "replace-ext": "^1.0.0", - "through2": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.0" - }, - "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - }, - "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 - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - } - } - }, - "gulp-notify": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", - "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "fancy-log": "^1.3.2", - "lodash.template": "^4.4.0", - "node-notifier": "^5.2.1", - "node.extend": "^2.0.0", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" - }, - "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - } - } - }, - "gulp-postcss": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-8.0.0.tgz", - "integrity": "sha512-Wtl6vH7a+8IS/fU5W9IbOpcaLqKxd5L1DUOzaPmlnCbX1CrG0aWdwVnC3Spn8th0m8D59YbysV5zPUe1n/GJYg==", - "dev": true, - "requires": { - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1", - "postcss": "^7.0.2", - "postcss-load-config": "^2.0.0", - "vinyl-sourcemaps-apply": "^0.2.1" - } - }, - "gulp-rename": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", - "dev": true - }, - "gulp-sort": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz", - "integrity": "sha1-xnYqLx8N4KP8WVohWZ0/rI26Gso=", - "dev": true, - "requires": { - "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.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "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", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "gulp-watch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.1.tgz", - "integrity": "sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog==", - "dev": true, - "requires": { - "ansi-colors": "1.1.0", - "anymatch": "^1.3.0", - "chokidar": "^2.0.0", - "fancy-log": "1.3.2", - "glob-parent": "^3.0.1", - "object-assign": "^4.1.0", - "path-is-absolute": "^1.0.1", - "plugin-error": "1.0.1", - "readable-stream": "^2.2.2", - "slash": "^1.0.0", - "vinyl": "^2.1.0", - "vinyl-file": "^2.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "fancy-log": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", - "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "time-stamp": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "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 - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "gulp-wrap": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", - "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", - "dev": true, - "requires": { - "consolidate": "^0.15.1", - "es6-promise": "^4.2.6", - "fs-readfile-promise": "^3.0.1", - "js-yaml": "^3.13.0", - "lodash": "^4.17.11", - "node.extend": "2.0.2", - "plugin-error": "^1.0.1", - "through2": "^3.0.1", - "tryit": "^1.0.1", - "vinyl-bufferstream": "^1.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } - } - } - }, - "gulp-wrap-js": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/gulp-wrap-js/-/gulp-wrap-js-0.4.1.tgz", - "integrity": "sha1-3uYqpISqupVHqT0f9c0MPQvtwDE=", - "dev": true, - "requires": { - "escodegen": "^1.6.1", - "esprima": "^2.3.0", - "estemplate": "*", - "gulp-util": "~3.0.5", - "through2": "*", - "vinyl-sourcemaps-apply": "^0.1.4" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "vinyl-sourcemaps-apply": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.1.4.tgz", - "integrity": "sha1-xfy9Q+LyOEI8LcmL3db3m3K8NFs=", - "dev": true, - "requires": { - "source-map": "^0.1.39" - } - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true, - "requires": { - "glogg": "^1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", - "dev": true, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true, - "optional": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "dev": true, - "optional": true, - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hasha": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", - "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", - "dev": true, - "requires": { - "is-stream": "^1.0.1", - "pinkie-promise": "^2.0.0" - } - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", - "dev": true - }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hosted-git-info": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", - "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", - "dev": true - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", - "dev": true - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", - "dev": true - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", - "dev": true - }, - "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true, - "optional": true - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true, - "optional": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, - "imagemin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.0.tgz", - "integrity": "sha512-TXvCSSIYl4KQUASur9S0+E4olVECzvxvZABU9rNqsza7vzIrUQMRTjyczGf8OmtcgvZ9jOYyinXW3epOpd/04A==", - "dev": true, - "requires": { - "file-type": "^12.0.0", - "globby": "^10.0.0", - "junk": "^3.1.0", - "make-dir": "^3.0.0", - "p-pipe": "^3.0.0", - "replace-ext": "^1.0.0" - }, - "dependencies": { - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - } - } - }, - "imagemin-gifsicle": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/imagemin-gifsicle/-/imagemin-gifsicle-6.0.1.tgz", - "integrity": "sha512-kuu47c6iKDQ6R9J10xCwL0lgs0+sMz3LRHqRcJ2CRBWdcNmo3T5hUaM8hSZfksptZXJLGKk8heSAvwtSdB1Fng==", - "dev": true, - "optional": true, - "requires": { - "exec-buffer": "^3.0.0", - "gifsicle": "^4.0.0", - "is-gif": "^3.0.0" - } - }, - "imagemin-jpegtran": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/imagemin-jpegtran/-/imagemin-jpegtran-6.0.0.tgz", - "integrity": "sha512-Ih+NgThzqYfEWv9t58EItncaaXIHR0u9RuhKa8CtVBlMBvY0dCIxgQJQCfwImA4AV1PMfmUKlkyIHJjb7V4z1g==", - "dev": true, - "optional": true, - "requires": { - "exec-buffer": "^3.0.0", - "is-jpg": "^2.0.0", - "jpegtran-bin": "^4.0.0" - } - }, - "imagemin-optipng": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-7.1.0.tgz", - "integrity": "sha512-JNORTZ6j6untH7e5gF4aWdhDCxe3ODsSLKs/f7Grewy3ebZpl1ZsU+VUTPY4rzeHgaFA8GSWOoA8V2M3OixWZQ==", - "dev": true, - "optional": true, - "requires": { - "exec-buffer": "^3.0.0", - "is-png": "^2.0.0", - "optipng-bin": "^6.0.0" - } - }, - "imagemin-svgo": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/imagemin-svgo/-/imagemin-svgo-7.0.0.tgz", - "integrity": "sha512-+iGJFaPIMx8TjFW6zN+EkOhlqcemdL7F3N3Y0wODvV2kCUBuUtZK7DRZc1+Zfu4U2W/lTMUyx2G8YMOrZntIWg==", - "dev": true, - "optional": true, - "requires": { - "is-svg": "^3.0.0", - "svgo": "^1.0.5" - } - }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dev": true, - "requires": { - "import-from": "^2.1.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "dev": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "import-lazy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", - "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", - "dev": true, - "optional": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "optional": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "indx": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/indx/-/indx-0.2.3.tgz", - "integrity": "sha1-Fdz1bunPZcAjTFE8J/vVgOcPvFA=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", - "dev": true - }, - "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.1.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } - } - } - } - }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "dev": true, - "optional": true, - "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "irregular-plurals": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", - "dev": true - }, - "is": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", - "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", - "dev": true - }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", - "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - } - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=", - "dev": true - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "dev": true, - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-gif": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", - "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", - "dev": true, - "optional": true, - "requires": { - "file-type": "^10.4.0" - }, - "dependencies": { - "file-type": { - "version": "10.11.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", - "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", - "dev": true, - "optional": true - } - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - }, - "is-jpg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-jpg/-/is-jpg-2.0.0.tgz", - "integrity": "sha1-LhmX+m6RZuqsAkLarkQ0A+TvHZc=", - "dev": true, - "optional": true - }, - "is-natural-number": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", - "dev": true, - "optional": true - }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true, - "optional": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true, - "optional": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-png": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", - "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", - "dev": true, - "optional": true - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", - "dev": true, - "requires": { - "is-unc-path": "^1.0.0" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", - "dev": true - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "dev": true, - "optional": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "dev": true, - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isbinaryfile": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", - "dev": true, - "requires": { - "buffer-alloc": "^1.2.0" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, - "optional": true, - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true - }, - "jpegtran-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-4.0.0.tgz", - "integrity": "sha512-2cRl1ism+wJUoYAYFt6O/rLBfpXNWG2dUWbgcEkTt5WGMnqI46eEro8T4C5zGROxKRqyKpCBSdHPvt5UYCtxaQ==", - "dev": true, - "optional": true, - "requires": { - "bin-build": "^3.0.0", - "bin-wrapper": "^4.0.0", - "logalot": "^2.0.0" - } - }, - "jquery": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", - "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" - }, - "jquery-ui-dist": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz", - "integrity": "sha1-XAgV08xvkP9fqvWyaKbiO0ypBPo=" - }, - "jquery-ui-touch-punch": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/jquery-ui-touch-punch/-/jquery-ui-touch-punch-0.2.3.tgz", - "integrity": "sha1-7tgiQnM7okP0az6HwYQbMIGR2mg=" - }, - "js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true, - "optional": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true, - "optional": true - } - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "dev": true - }, - "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", - "dev": true - }, - "karma": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-4.4.1.tgz", - "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", - "dev": true, - "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "2.1.1", - "source-map": "^0.6.1", - "tmp": "0.0.33", - "useragent": "2.3.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.2.2.tgz", - "integrity": "sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fsevents": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.1.tgz", - "integrity": "sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==", - "dev": true, - "optional": true - }, - "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.4" - } - }, - "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 - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "karma-jasmine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", - "dev": true, - "requires": { - "jasmine-core": "^3.3" - } - }, - "karma-junit-reporter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", - "integrity": "sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==", - "dev": true, - "requires": { - "path-is-absolute": "^1.0.0", - "xmlbuilder": "12.0.0" - } - }, - "karma-phantomjs-launcher": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz", - "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=", - "dev": true, - "requires": { - "lodash": "^4.0.1", - "phantomjs-prebuilt": "^2.1.7" - } - }, - "karma-spec-reporter": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", - "integrity": "sha1-LpxyB+pyZ3EmAln4K+y1QyCeRAo=", - "dev": true, - "requires": { - "colors": "^1.1.2" - } - }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", - "dev": true - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "dev": true, - "optional": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", - "dev": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true, - "optional": true - } - } - }, - "kuler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "dev": true, - "requires": { - "colornames": "^1.1.1" - } - }, - "last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, - "lazyload-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazyload-js/-/lazyload-js-1.0.0.tgz", - "integrity": "sha1-jBA5sbaRec1J/cMkICOvSM4IOSU=" - }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", - "dev": true, - "requires": { - "flush-write-stream": "^1.0.2" - } - }, - "less": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/less/-/less-3.10.3.tgz", - "integrity": "sha512-vz32vqfgmoxF1h3K4J+yKCtajH0PWmjkIFgbs5d78E/c/e+UQTnI+lWK+1eQRE95PXM2mC3rJlLSSP9VQHnaow==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "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, - "optional": true - } - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.clone": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", - "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", - "dev": true - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.partialright": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.partialright/-/lodash.partialright-4.2.1.tgz", - "integrity": "sha1-ATDYDoM2MmTUAHTzKbij56ihzEs=", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "log4js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz", - "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", - "dev": true, - "requires": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" - } - }, - "logalot": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz", - "integrity": "sha1-X46MkNME7fElMJUaVVSruMXj9VI=", - "dev": true, - "optional": true, - "requires": { - "figures": "^1.3.5", - "squeak": "^1.0.0" - }, - "dependencies": { - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "optional": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "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, - "optional": true - } - } - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "optional": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true, - "optional": true - }, - "lpad-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.2.tgz", - "integrity": "sha1-IfYArBwwlcPG5JfuZyce4ISB/p4=", - "dev": true, - "optional": true, - "requires": { - "get-stdin": "^4.0.1", - "indent-string": "^2.1.0", - "longest": "^1.0.0", - "meow": "^3.3.0" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "dev": true, - "requires": { - "es5-ext": "~0.10.2" - } - }, - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "optional": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true - }, - "matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", - "dev": true, - "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "optional": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "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, - "optional": true - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "optional": true - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", - "dev": true - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "dev": true, - "requires": { - "mime-db": "1.40.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", - "dev": true - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "minimize": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz", - "integrity": "sha512-IxR2XMbw9pXCxApkdD9BTcH2U4XlXhbeySUrv71rmMS9XDA8BVXEsIuFu24LtwCfBgfbL7Fuh8/ZzkO5DaTLlQ==", - "dev": true, - "requires": { - "argh": "^0.1.4", - "async": "^2.1.5", - "cli-color": "^1.2.0", - "diagnostics": "^1.1.0", - "emits": "^3.0.0", - "htmlparser2": "^3.9.2", - "uuid": "^3.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } - } - }, - "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, - "mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "ng-file-upload": { - "version": "12.2.13", - "resolved": "https://registry.npmjs.org/ng-file-upload/-/ng-file-upload-12.2.13.tgz", - "integrity": "sha1-AYAPOHLlJvlTEPhHfpnk8S0NjRQ=" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-notifier": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "node-releases": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.32.tgz", - "integrity": "sha512-VhVknkitq8dqtWoluagsGPn3dxTvN9fwgR59fV3D7sLBHe0JfDramsMI8n8mY//ccq/Kkrf8ZRHRpsyVZ3qw1A==", - "dev": true, - "requires": { - "semver": "^5.3.0" - } - }, - "node.extend": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", - "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", - "dev": true, - "requires": { - "has": "^1.0.3", - "is": "^3.2.1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", - "dev": true - }, - "nouislider": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.1.1.tgz", - "integrity": "sha512-3/+Z/pTBoWoJf2YXSEWRmS27LW2XxOBmGEzkPyRzB/J6QvL+0mS3QwcQp0SmWhgO5CMzbSxPmb1lDDD4HP12bg==" - }, - "now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "requires": { - "once": "^1.3.2" - } - }, - "npm": { - "version": "6.13.6", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.6.tgz", - "integrity": "sha512-NomC08kv7HIl1FOyLOe9Hp89kYsOsvx52huVIJ7i8hFW8Xp65lDwe/8wTIrh9q9SaQhA8hTrfXPh3BEL3TmMpw==", - "requires": { - "JSONStream": "^1.3.5", - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "aproba": "^2.0.0", - "archy": "~1.0.0", - "bin-links": "^1.1.6", - "bluebird": "^3.5.5", - "byte-size": "^5.0.1", - "cacache": "^12.0.3", - "call-limit": "^1.1.1", - "chownr": "^1.1.3", - "ci-info": "^2.0.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.5.1", - "cmd-shim": "^3.0.3", - "columnify": "~1.5.4", - "config-chain": "^1.1.12", - "debuglog": "*", - "detect-indent": "~5.0.0", - "detect-newline": "^2.1.0", - "dezalgo": "~1.0.3", - "editor": "~1.0.0", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "fs-vacuum": "~1.2.10", - "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.3.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", - "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.5", - "iferr": "^1.0.2", - "imurmurhash": "*", - "infer-owner": "^1.0.4", - "inflight": "~1.0.6", - "inherits": "^2.0.4", - "ini": "^1.3.5", - "init-package-json": "^1.10.3", - "is-cidr": "^3.0.0", - "json-parse-better-errors": "^1.0.2", - "lazy-property": "~1.0.0", - "libcipm": "^4.0.7", - "libnpm": "^3.0.1", - "libnpmaccess": "^3.0.2", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "libnpx": "^10.2.0", - "lock-verify": "^2.1.0", - "lockfile": "^1.0.4", - "lodash._baseindexof": "*", - "lodash._baseuniq": "~4.6.0", - "lodash._bindcallback": "*", - "lodash._cacheindexof": "*", - "lodash._createcache": "*", - "lodash._getnative": "*", - "lodash.clonedeep": "~4.5.0", - "lodash.restparam": "*", - "lodash.union": "~4.6.0", - "lodash.uniq": "~4.5.0", - "lodash.without": "~4.4.0", - "lru-cache": "^5.1.1", - "meant": "~1.0.1", - "mississippi": "^3.0.0", - "mkdirp": "~0.5.1", - "move-concurrently": "^1.0.1", - "node-gyp": "^5.0.5", - "nopt": "~4.0.1", - "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.2", - "npm-cache-filename": "~1.0.2", - "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.4", - "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.7", - "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.2", - "npm-user-validate": "~1.0.0", - "npmlog": "~4.1.2", - "once": "~1.4.0", - "opener": "^1.5.1", - "osenv": "^0.1.5", - "pacote": "^9.5.12", - "path-is-inside": "~1.0.2", - "promise-inflight": "~1.0.1", - "qrcode-terminal": "^0.12.0", - "query-string": "^6.8.2", - "qw": "~1.0.1", - "read": "~1.0.7", - "read-cmd-shim": "^1.0.5", - "read-installed": "~4.0.3", - "read-package-json": "^2.1.1", - "read-package-tree": "^5.3.1", - "readable-stream": "^3.4.0", - "readdir-scoped-modules": "^1.1.0", - "request": "^2.88.0", - "retry": "^0.12.0", - "rimraf": "^2.6.3", - "safe-buffer": "^5.1.2", - "semver": "^5.7.1", - "sha": "^3.0.0", - "slide": "~1.1.6", - "sorted-object": "~2.0.1", - "sorted-union-stream": "~2.1.3", - "ssri": "^6.0.1", - "stringify-package": "^1.0.1", - "tar": "^4.4.13", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "uid-number": "0.0.6", - "umask": "~1.1.0", - "unique-filename": "^1.1.1", - "unpipe": "~1.0.0", - "update-notifier": "^2.5.0", - "uuid": "^3.3.3", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^1.3.1", - "worker-farm": "^1.7.0", - "write-file-atomic": "^2.4.3" - }, - "dependencies": { - "JSONStream": { - "version": "1.3.5", - "bundled": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "agent-base": { - "version": "4.3.0", - "bundled": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "agentkeepalive": { - "version": "3.5.2", - "bundled": true, - "requires": { - "humanize-ms": "^1.2.1" - } - }, - "ajv": { - "version": "5.5.2", - "bundled": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ansi-align": { - "version": "2.0.0", - "bundled": true, - "requires": { - "string-width": "^2.0.0" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "ansi-styles": { - "version": "3.2.1", - "bundled": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "ansicolors": { - "version": "0.3.2", - "bundled": true - }, - "ansistyles": { - "version": "0.1.3", - "bundled": true - }, - "aproba": { - "version": "2.0.0", - "bundled": true - }, - "archy": { - "version": "1.0.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "asap": { - "version": "2.0.6", - "bundled": true - }, - "asn1": { - "version": "0.2.4", - "bundled": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "bundled": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true - }, - "aws-sign2": { - "version": "0.7.0", - "bundled": true - }, - "aws4": { - "version": "1.8.0", - "bundled": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bin-links": { - "version": "1.1.6", - "bundled": true, - "requires": { - "bluebird": "^3.5.3", - "cmd-shim": "^3.0.0", - "gentle-fs": "^2.3.0", - "graceful-fs": "^4.1.15", - "npm-normalize-package-bin": "^1.0.0", - "write-file-atomic": "^2.3.0" - } - }, - "bluebird": { - "version": "3.5.5", - "bundled": true - }, - "boxen": { - "version": "1.3.0", - "bundled": true, - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-from": { - "version": "1.0.0", - "bundled": true - }, - "builtins": { - "version": "1.0.3", - "bundled": true - }, - "byline": { - "version": "5.0.0", - "bundled": true - }, - "byte-size": { - "version": "5.0.1", - "bundled": true - }, - "cacache": { - "version": "12.0.3", - "bundled": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "call-limit": { - "version": "1.1.1", - "bundled": true - }, - "camelcase": { - "version": "4.1.0", - "bundled": true - }, - "capture-stack-trace": { - "version": "1.0.0", - "bundled": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true - }, - "chalk": { - "version": "2.4.1", - "bundled": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chownr": { - "version": "1.1.3", - "bundled": true - }, - "ci-info": { - "version": "2.0.0", - "bundled": true - }, - "cidr-regex": { - "version": "2.0.10", - "bundled": true, - "requires": { - "ip-regex": "^2.1.0" - } - }, - "cli-boxes": { - "version": "1.0.0", - "bundled": true - }, - "cli-columns": { - "version": "3.1.2", - "bundled": true, - "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" - } - }, - "cli-table3": { - "version": "0.5.1", - "bundled": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - } - }, - "cliui": { - "version": "4.1.0", - "bundled": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "bundled": true - }, - "cmd-shim": { - "version": "3.0.3", - "bundled": true, - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, - "co": { - "version": "4.6.0", - "bundled": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "color-convert": { - "version": "1.9.1", - "bundled": true, - "requires": { - "color-name": "^1.1.1" - } - }, - "color-name": { - "version": "1.1.3", - "bundled": true - }, - "colors": { - "version": "1.3.3", - "bundled": true, - "optional": true - }, - "columnify": { - "version": "1.5.4", - "bundled": true, - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - } - }, - "combined-stream": { - "version": "1.0.6", - "bundled": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "concat-stream": { - "version": "1.6.2", - "bundled": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "config-chain": { - "version": "1.1.12", - "bundled": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "configstore": { - "version": "3.1.2", - "bundled": true, - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "copy-concurrently": { - "version": "1.0.5", - "bundled": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "iferr": { - "version": "0.1.5", - "bundled": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "create-error-class": { - "version": "3.0.2", - "bundled": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "5.1.0", - "bundled": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.5", - "bundled": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "yallist": { - "version": "2.1.2", - "bundled": true - } - } - }, - "crypto-random-string": { - "version": "1.0.0", - "bundled": true - }, - "cyclist": { - "version": "0.2.2", - "bundled": true - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.1.0", - "bundled": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "bundled": true - } - } - }, - "debuglog": { - "version": "1.0.1", - "bundled": true - }, - "decamelize": { - "version": "1.2.0", - "bundled": true - }, - "decode-uri-component": { - "version": "0.2.0", - "bundled": true - }, - "deep-extend": { - "version": "0.5.1", - "bundled": true - }, - "defaults": { - "version": "1.0.3", - "bundled": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.3", - "bundled": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "detect-indent": { - "version": "5.0.0", - "bundled": true - }, - "detect-newline": { - "version": "2.1.0", - "bundled": true - }, - "dezalgo": { - "version": "1.0.3", - "bundled": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "dot-prop": { - "version": "4.2.0", - "bundled": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "dotenv": { - "version": "5.0.1", - "bundled": true - }, - "duplexer3": { - "version": "0.1.4", - "bundled": true - }, - "duplexify": { - "version": "3.6.0", - "bundled": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "bundled": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "editor": { - "version": "1.0.0", - "bundled": true - }, - "encoding": { - "version": "0.1.12", - "bundled": true, - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "end-of-stream": { - "version": "1.4.1", - "bundled": true, - "requires": { - "once": "^1.4.0" - } - }, - "env-paths": { - "version": "1.0.0", - "bundled": true - }, - "err-code": { - "version": "1.1.2", - "bundled": true - }, - "errno": { - "version": "0.1.7", - "bundled": true, - "requires": { - "prr": "~1.0.1" - } - }, - "es-abstract": { - "version": "1.12.0", - "bundled": true, - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "bundled": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-promise": { - "version": "4.2.8", - "bundled": true - }, - "es6-promisify": { - "version": "5.0.0", - "bundled": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "bundled": true - }, - "execa": { - "version": "0.7.0", - "bundled": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "bundled": true - } - } - }, - "extend": { - "version": "3.0.2", - "bundled": true - }, - "extsprintf": { - "version": "1.3.0", - "bundled": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "bundled": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "bundled": true - }, - "figgy-pudding": { - "version": "3.5.1", - "bundled": true - }, - "find-npm-prefix": { - "version": "1.0.2", - "bundled": true - }, - "find-up": { - "version": "2.1.0", - "bundled": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flush-write-stream": { - "version": "1.0.3", - "bundled": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true - }, - "form-data": { - "version": "2.3.2", - "bundled": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, - "from2": { - "version": "2.3.0", - "bundled": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "requires": { - "minipass": "^2.6.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "fs-vacuum": { - "version": "1.2.10", - "bundled": true, - "requires": { - "graceful-fs": "^4.1.2", - "path-is-inside": "^1.0.1", - "rimraf": "^2.5.2" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "bundled": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - }, - "dependencies": { - "iferr": { - "version": "0.1.5", - "bundled": true - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "function-bind": { - "version": "1.1.1", - "bundled": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "genfun": { - "version": "5.0.0", - "bundled": true - }, - "gentle-fs": { - "version": "2.3.0", - "bundled": true, - "requires": { - "aproba": "^1.1.2", - "chownr": "^1.1.2", - "cmd-shim": "^3.0.3", - "fs-vacuum": "^1.2.10", - "graceful-fs": "^4.1.11", - "iferr": "^0.1.5", - "infer-owner": "^1.0.4", - "mkdirp": "^0.5.1", - "path-is-inside": "^1.0.2", - "read-cmd-shim": "^1.0.1", - "slide": "^1.1.6" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "iferr": { - "version": "0.1.5", - "bundled": true - } - } - }, - "get-caller-file": { - "version": "1.0.2", - "bundled": true - }, - "get-stream": { - "version": "4.1.0", - "bundled": true, - "requires": { - "pump": "^3.0.0" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.4", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "global-dirs": { - "version": "0.1.1", - "bundled": true, - "requires": { - "ini": "^1.3.4" - } - }, - "got": { - "version": "6.7.1", - "bundled": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "bundled": true - } - } - }, - "graceful-fs": { - "version": "4.2.3", - "bundled": true - }, - "har-schema": { - "version": "2.0.0", - "bundled": true - }, - "har-validator": { - "version": "5.1.0", - "bundled": true, - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "bundled": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "bundled": true - }, - "has-symbols": { - "version": "1.0.0", - "bundled": true - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "hosted-git-info": { - "version": "2.8.5", - "bundled": true - }, - "http-cache-semantics": { - "version": "3.8.1", - "bundled": true - }, - "http-proxy-agent": { - "version": "2.1.0", - "bundled": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - } - }, - "http-signature": { - "version": "1.2.0", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "bundled": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - }, - "humanize-ms": { - "version": "1.2.1", - "bundled": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.23", - "bundled": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "iferr": { - "version": "1.0.2", - "bundled": true - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "import-lazy": { - "version": "2.1.0", - "bundled": true - }, - "imurmurhash": { - "version": "0.1.4", - "bundled": true - }, - "infer-owner": { - "version": "1.0.4", - "bundled": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true - }, - "init-package-json": { - "version": "1.10.3", - "bundled": true, - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "bundled": true - }, - "ip": { - "version": "1.1.5", - "bundled": true - }, - "ip-regex": { - "version": "2.1.0", - "bundled": true - }, - "is-callable": { - "version": "1.1.4", - "bundled": true - }, - "is-ci": { - "version": "1.1.0", - "bundled": true, - "requires": { - "ci-info": "^1.0.0" - }, - "dependencies": { - "ci-info": { - "version": "1.6.0", - "bundled": true - } - } - }, - "is-cidr": { - "version": "3.0.0", - "bundled": true, - "requires": { - "cidr-regex": "^2.0.10" - } - }, - "is-date-object": { - "version": "1.0.1", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-installed-globally": { - "version": "0.1.0", - "bundled": true, - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, - "is-npm": { - "version": "1.0.0", - "bundled": true - }, - "is-obj": { - "version": "1.0.1", - "bundled": true - }, - "is-path-inside": { - "version": "1.0.1", - "bundled": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-redirect": { - "version": "1.0.0", - "bundled": true - }, - "is-regex": { - "version": "1.0.4", - "bundled": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-retry-allowed": { - "version": "1.1.0", - "bundled": true - }, - "is-stream": { - "version": "1.1.0", - "bundled": true - }, - "is-symbol": { - "version": "1.0.2", - "bundled": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "isexe": { - "version": "2.0.0", - "bundled": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "optional": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "bundled": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "bundled": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true - }, - "jsonparse": { - "version": "1.3.1", - "bundled": true - }, - "jsprim": { - "version": "1.4.1", - "bundled": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "latest-version": { - "version": "3.1.0", - "bundled": true, - "requires": { - "package-json": "^4.0.0" - } - }, - "lazy-property": { - "version": "1.0.0", - "bundled": true - }, - "lcid": { - "version": "1.0.0", - "bundled": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "libcipm": { - "version": "4.0.7", - "bundled": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.5.1", - "find-npm-prefix": "^1.0.2", - "graceful-fs": "^4.1.11", - "ini": "^1.3.5", - "lock-verify": "^2.0.2", - "mkdirp": "^0.5.1", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "pacote": "^9.1.0", - "read-package-json": "^2.0.13", - "rimraf": "^2.6.2", - "worker-farm": "^1.6.0" - } - }, - "libnpm": { - "version": "3.0.1", - "bundled": true, - "requires": { - "bin-links": "^1.1.2", - "bluebird": "^3.5.3", - "find-npm-prefix": "^1.0.2", - "libnpmaccess": "^3.0.2", - "libnpmconfig": "^1.2.1", - "libnpmhook": "^5.0.3", - "libnpmorg": "^1.0.1", - "libnpmpublish": "^1.1.2", - "libnpmsearch": "^2.0.2", - "libnpmteam": "^1.0.2", - "lock-verify": "^2.0.2", - "npm-lifecycle": "^3.0.0", - "npm-logical-tree": "^1.2.1", - "npm-package-arg": "^6.1.0", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.0", - "npmlog": "^4.1.2", - "pacote": "^9.5.3", - "read-package-json": "^2.0.13", - "stringify-package": "^1.0.0" - } - }, - "libnpmaccess": { - "version": "3.0.2", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmconfig": { - "version": "1.2.1", - "bundled": true, - "requires": { - "figgy-pudding": "^3.5.1", - "find-up": "^3.0.0", - "ini": "^1.3.5" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "bundled": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "bundled": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "bundled": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "bundled": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "bundled": true - } - } - }, - "libnpmhook": { - "version": "5.0.3", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmorg": { - "version": "1.0.1", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmpublish": { - "version": "1.1.2", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^4.0.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" - } - }, - "libnpmsearch": { - "version": "2.0.2", - "bundled": true, - "requires": { - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpmteam": { - "version": "1.0.2", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.4.1", - "get-stream": "^4.0.0", - "npm-registry-fetch": "^4.0.0" - } - }, - "libnpx": { - "version": "10.2.0", - "bundled": true, - "requires": { - "dotenv": "^5.0.1", - "npm-package-arg": "^6.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.0", - "update-notifier": "^2.3.0", - "which": "^1.3.0", - "y18n": "^4.0.0", - "yargs": "^11.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "bundled": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lock-verify": { - "version": "2.1.0", - "bundled": true, - "requires": { - "npm-package-arg": "^6.1.0", - "semver": "^5.4.1" - } - }, - "lockfile": { - "version": "1.0.4", - "bundled": true, - "requires": { - "signal-exit": "^3.0.2" - } - }, - "lodash._baseindexof": { - "version": "3.1.0", - "bundled": true - }, - "lodash._baseuniq": { - "version": "4.6.0", - "bundled": true, - "requires": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "lodash._bindcallback": { - "version": "3.0.1", - "bundled": true - }, - "lodash._cacheindexof": { - "version": "3.0.2", - "bundled": true - }, - "lodash._createcache": { - "version": "3.1.2", - "bundled": true, - "requires": { - "lodash._getnative": "^3.0.0" - } - }, - "lodash._createset": { - "version": "4.0.3", - "bundled": true - }, - "lodash._getnative": { - "version": "3.9.1", - "bundled": true - }, - "lodash._root": { - "version": "3.0.1", - "bundled": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "bundled": true - }, - "lodash.restparam": { - "version": "3.6.1", - "bundled": true - }, - "lodash.union": { - "version": "4.6.0", - "bundled": true - }, - "lodash.uniq": { - "version": "4.5.0", - "bundled": true - }, - "lodash.without": { - "version": "4.4.0", - "bundled": true - }, - "lowercase-keys": { - "version": "1.0.1", - "bundled": true - }, - "lru-cache": { - "version": "5.1.1", - "bundled": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "1.3.0", - "bundled": true, - "requires": { - "pify": "^3.0.0" - } - }, - "make-fetch-happen": { - "version": "5.0.2", - "bundled": true, - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^12.0.0", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - }, - "meant": { - "version": "1.0.1", - "bundled": true - }, - "mem": { - "version": "1.1.0", - "bundled": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "mime-db": { - "version": "1.35.0", - "bundled": true - }, - "mime-types": { - "version": "2.1.19", - "bundled": true, - "requires": { - "mime-db": "~1.35.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "requires": { - "minipass": "^2.9.0" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "mississippi": { - "version": "3.0.0", - "bundled": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "move-concurrently": { - "version": "1.0.1", - "bundled": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true - } - } - }, - "ms": { - "version": "2.1.1", - "bundled": true - }, - "mute-stream": { - "version": "0.0.7", - "bundled": true - }, - "node-fetch-npm": { - "version": "2.0.2", - "bundled": true, - "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" - } - }, - "node-gyp": { - "version": "5.0.5", - "bundled": true, - "requires": { - "env-paths": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^4.4.12", - "which": "1" - }, - "dependencies": { - "nopt": { - "version": "3.0.6", - "bundled": true, - "requires": { - "abbrev": "1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true - } - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "bundled": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "resolve": { - "version": "1.10.0", - "bundled": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "npm-audit-report": { - "version": "1.3.2", - "bundled": true, - "requires": { - "cli-table3": "^0.5.0", - "console-control-strings": "^1.1.0" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-cache-filename": { - "version": "1.0.2", - "bundled": true - }, - "npm-install-checks": { - "version": "3.0.2", - "bundled": true, - "requires": { - "semver": "^2.3.0 || 3.x || 4 || 5" - } - }, - "npm-lifecycle": { - "version": "3.1.4", - "bundled": true, - "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.15", - "node-gyp": "^5.0.2", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" - } - }, - "npm-logical-tree": { - "version": "1.2.1", - "bundled": true - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true - }, - "npm-package-arg": { - "version": "6.1.1", - "bundled": true, - "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "npm-packlist": { - "version": "1.4.7", - "bundled": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npm-pick-manifest": { - "version": "3.0.2", - "bundled": true, - "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" - } - }, - "npm-profile": { - "version": "4.0.2", - "bundled": true, - "requires": { - "aproba": "^1.1.2 || 2", - "figgy-pudding": "^3.4.1", - "npm-registry-fetch": "^4.0.0" - } - }, - "npm-registry-fetch": { - "version": "4.0.2", - "bundled": true, - "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "npm-package-arg": "^6.1.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "bundled": true - } - } - }, - "npm-run-path": { - "version": "2.0.2", - "bundled": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npm-user-validate": { - "version": "1.0.0", - "bundled": true - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "oauth-sign": { - "version": "0.9.0", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "object-keys": { - "version": "1.0.12", - "bundled": true - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "bundled": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.1", - "bundled": true - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true - }, - "os-locale": { - "version": "2.1.0", - "bundled": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "bundled": true - }, - "p-limit": { - "version": "1.2.0", - "bundled": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "bundled": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "bundled": true - }, - "package-json": { - "version": "4.0.1", - "bundled": true, - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "pacote": { - "version": "9.5.12", - "bundled": true, - "requires": { - "bluebird": "^3.5.3", - "cacache": "^12.0.2", - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "infer-owner": "^1.0.4", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^5.0.0", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-normalize-package-bin": "^1.0.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^3.0.0", - "npm-registry-fetch": "^4.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.10", - "unique-filename": "^1.1.1", - "which": "^1.3.1" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "parallel-transform": { - "version": "1.1.0", - "bundled": true, - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "path-exists": { - "version": "3.0.0", - "bundled": true - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "path-is-inside": { - "version": "1.0.2", - "bundled": true - }, - "path-key": { - "version": "2.0.1", - "bundled": true - }, - "path-parse": { - "version": "1.0.6", - "bundled": true - }, - "performance-now": { - "version": "2.1.0", - "bundled": true - }, - "pify": { - "version": "3.0.0", - "bundled": true - }, - "prepend-http": { - "version": "1.0.4", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true - }, - "promise-inflight": { - "version": "1.0.1", - "bundled": true - }, - "promise-retry": { - "version": "1.1.1", - "bundled": true, - "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" - }, - "dependencies": { - "retry": { - "version": "0.10.1", - "bundled": true - } - } - }, - "promzard": { - "version": "0.3.0", - "bundled": true, - "requires": { - "read": "1" - } - }, - "proto-list": { - "version": "1.2.4", - "bundled": true - }, - "protoduck": { - "version": "5.0.1", - "bundled": true, - "requires": { - "genfun": "^5.0.0" - } - }, - "prr": { - "version": "1.0.1", - "bundled": true - }, - "pseudomap": { - "version": "1.0.2", - "bundled": true - }, - "psl": { - "version": "1.1.29", - "bundled": true - }, - "pump": { - "version": "3.0.0", - "bundled": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "bundled": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "bundled": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "1.4.1", - "bundled": true - }, - "qrcode-terminal": { - "version": "0.12.0", - "bundled": true - }, - "qs": { - "version": "6.5.2", - "bundled": true - }, - "query-string": { - "version": "6.8.2", - "bundled": true, - "requires": { - "decode-uri-component": "^0.2.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, - "qw": { - "version": "1.0.1", - "bundled": true - }, - "rc": { - "version": "1.2.7", - "bundled": true, - "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } - } - }, - "read": { - "version": "1.0.7", - "bundled": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-cmd-shim": { - "version": "1.0.5", - "bundled": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "read-installed": { - "version": "4.0.3", - "bundled": true, - "requires": { - "debuglog": "^1.0.1", - "graceful-fs": "^4.1.2", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - } - }, - "read-package-json": { - "version": "2.1.1", - "bundled": true, - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "read-package-tree": { - "version": "5.3.1", - "bundled": true, - "requires": { - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "util-promisify": "^2.1.0" - } - }, - "readable-stream": { - "version": "3.4.0", - "bundled": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdir-scoped-modules": { - "version": "1.1.0", - "bundled": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "registry-auth-token": { - "version": "3.3.2", - "bundled": true, - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "bundled": true, - "requires": { - "rc": "^1.0.1" - } - }, - "request": { - "version": "2.88.0", - "bundled": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "bundled": true - }, - "require-main-filename": { - "version": "1.0.1", - "bundled": true - }, - "resolve-from": { - "version": "4.0.0", - "bundled": true - }, - "retry": { - "version": "0.12.0", - "bundled": true - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-queue": { - "version": "1.0.3", - "bundled": true, - "requires": { - "aproba": "^1.1.1" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true - }, - "semver": { - "version": "5.7.1", - "bundled": true - }, - "semver-diff": { - "version": "2.1.0", - "bundled": true, - "requires": { - "semver": "^5.0.3" - } - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "sha": { - "version": "3.0.0", - "bundled": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "bundled": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true - }, - "slide": { - "version": "1.1.6", - "bundled": true - }, - "smart-buffer": { - "version": "4.1.0", - "bundled": true - }, - "socks": { - "version": "2.3.3", - "bundled": true, - "requires": { - "ip": "1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "4.0.2", - "bundled": true, - "requires": { - "agent-base": "~4.2.1", - "socks": "~2.3.2" - }, - "dependencies": { - "agent-base": { - "version": "4.2.1", - "bundled": true, - "requires": { - "es6-promisify": "^5.0.0" - } - } - } - }, - "sorted-object": { - "version": "2.0.1", - "bundled": true - }, - "sorted-union-stream": { - "version": "2.1.3", - "bundled": true, - "requires": { - "from2": "^1.3.0", - "stream-iterate": "^1.1.0" - }, - "dependencies": { - "from2": { - "version": "1.3.0", - "bundled": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~1.1.10" - } - }, - "isarray": { - "version": "0.0.1", - "bundled": true - }, - "readable-stream": { - "version": "1.1.14", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "bundled": true - } - } - }, - "spdx-correct": { - "version": "3.0.0", - "bundled": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "bundled": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "bundled": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.3", - "bundled": true - }, - "split-on-first": { - "version": "1.1.0", - "bundled": true - }, - "sshpk": { - "version": "1.14.2", - "bundled": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "6.0.1", - "bundled": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "stream-each": { - "version": "1.2.2", - "bundled": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-iterate": { - "version": "1.2.0", - "bundled": true, - "requires": { - "readable-stream": "^2.1.5", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-shift": { - "version": "1.0.0", - "bundled": true - }, - "strict-uri-encode": { - "version": "2.0.0", - "bundled": true - }, - "string-width": { - "version": "2.1.1", - "bundled": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.2.0", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-package": { - "version": "1.0.1", - "bundled": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "bundled": true - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true - }, - "supports-color": { - "version": "5.4.0", - "bundled": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "minipass": { - "version": "2.9.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - } - } - }, - "term-size": { - "version": "1.2.0", - "bundled": true, - "requires": { - "execa": "^0.7.0" - } - }, - "text-table": { - "version": "0.2.0", - "bundled": true - }, - "through": { - "version": "2.3.8", - "bundled": true - }, - "through2": { - "version": "2.0.3", - "bundled": true, - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "timed-out": { - "version": "4.0.1", - "bundled": true - }, - "tiny-relative-date": { - "version": "1.3.0", - "bundled": true - }, - "tough-cookie": { - "version": "2.4.3", - "bundled": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "optional": true - }, - "typedarray": { - "version": "0.0.6", - "bundled": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true - }, - "umask": { - "version": "1.1.0", - "bundled": true - }, - "unique-filename": { - "version": "1.1.1", - "bundled": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.0", - "bundled": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "unique-string": { - "version": "1.0.0", - "bundled": true, - "requires": { - "crypto-random-string": "^1.0.0" - } - }, - "unpipe": { - "version": "1.0.0", - "bundled": true - }, - "unzip-response": { - "version": "2.0.1", - "bundled": true - }, - "update-notifier": { - "version": "2.5.0", - "bundled": true, - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "bundled": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "util-extend": { - "version": "1.0.3", - "bundled": true - }, - "util-promisify": { - "version": "2.1.0", - "bundled": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "uuid": { - "version": "3.3.3", - "bundled": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "bundled": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "3.0.0", - "bundled": true, - "requires": { - "builtins": "^1.0.3" - } - }, - "verror": { - "version": "1.10.0", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "wcwidth": { - "version": "1.0.1", - "bundled": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "which": { - "version": "1.3.1", - "bundled": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "bundled": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "requires": { - "string-width": "^1.0.2" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "widest-line": { - "version": "2.0.0", - "bundled": true, - "requires": { - "string-width": "^2.1.1" - } - }, - "worker-farm": { - "version": "1.7.0", - "bundled": true, - "requires": { - "errno": "~0.1.7" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "bundled": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "write-file-atomic": { - "version": "2.4.3", - "bundled": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "bundled": true - }, - "xtend": { - "version": "4.0.1", - "bundled": true - }, - "y18n": { - "version": "4.0.0", - "bundled": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true - }, - "yargs": { - "version": "11.0.0", - "bundled": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - }, - "dependencies": { - "y18n": { - "version": "3.2.1", - "bundled": true - } - } - }, - "yargs-parser": { - "version": "9.0.2", - "bundled": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "npm-conf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", - "dev": true, - "optional": true, - "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "optional": true - } - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "optional": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - } - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", - "dev": true, - "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - } - }, - "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "optipng-bin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-6.0.0.tgz", - "integrity": "sha512-95bB4y8IaTsa/8x6QH4bLUuyvyOoGBCLDA7wOgDL8UFqJpSUh1Hob8JRJhit+wC1ZLN3tQ7mFt7KuBj0x8F2Wg==", - "dev": true, - "optional": true, - "requires": { - "bin-build": "^3.0.0", - "bin-wrapper": "^4.0.0", - "logalot": "^2.0.0" - } - }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "os-filter-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", - "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", - "dev": true, - "optional": true, - "requires": { - "arch": "^2.1.0" - } - }, - "os-locale": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", - "dev": true, - "optional": true - }, - "p-event": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", - "integrity": "sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=", - "dev": true, - "optional": true, - "requires": { - "p-timeout": "^1.1.1" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "optional": true - }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true, - "optional": true - }, - "p-map-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", - "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=", - "dev": true, - "optional": true, - "requires": { - "p-reduce": "^1.0.0" - } - }, - "p-pipe": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-3.0.0.tgz", - "integrity": "sha512-gwwdRFmaxsT3IU+Tl3vYKVRdjfhg8Bbdjw7B+E0y6F7Yz6l+eaQLn0BRmGMXIhcPDONPtOkMoNwx1etZh4zPJA==", - "dev": true - }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true, - "optional": true - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dev": true, - "optional": true, - "requires": { - "p-finally": "^1.0.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - }, - "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - } - } - }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "requires": { - "path-root-regex": "^0.1.0" - } - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true - } - } - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "phantomjs-prebuilt": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", - "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3", - "extract-zip": "^1.6.5", - "fs-extra": "^1.0.0", - "hasha": "^2.2.0", - "kew": "^0.7.0", - "progress": "^1.1.8", - "request": "^2.81.0", - "request-progress": "^2.0.1", - "which": "^1.2.10" - }, - "dependencies": { - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - } - } - }, - "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - } - }, - "plur": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", - "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", - "dev": true, - "requires": { - "irregular-plurals": "^2.0.0" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "7.0.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.18.tgz", - "integrity": "sha512-/7g1QXXgegpF+9GJj4iN7ChGF40sYuGYJ8WZu8DZWnmhQ/G36hfdk3q9LBJmoK+lZ+yzZ5KYpOoxq7LF1BxE8g==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.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 - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-calc": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", - "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", - "dev": true, - "requires": { - "css-unit-converter": "^1.1.1", - "postcss": "^7.0.5", - "postcss-selector-parser": "^5.0.0-rc.4", - "postcss-value-parser": "^3.3.1" - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - } - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dev": true, - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "dev": true, - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "dev": true, - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "dev": true, - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "dev": true, - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "dev": true, - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dev": true, - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "dev": true, - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "dev": true, - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "dev": true, - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - } - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true, - "optional": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-bytes": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", - "integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==", - "dev": true - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", - "dev": true, - "optional": true, - "requires": { - "asap": "~2.0.3" - } - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true, - "optional": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true, - "optional": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", - "dev": true - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, - "optional": true, - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "dependencies": { - "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, - "optional": true - } - } - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "optional": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", - "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", - "dev": true, - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-transform": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", - "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", - "dev": true, - "requires": { - "private": "^0.1.6" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "regexpu-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", - "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.1.0", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", - "dev": true - }, - "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - } - }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", - "dev": true, - "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "optional": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - } - } - }, - "request-progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", - "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "requires": { - "value-or-function": "^3.0.0" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "optional": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", - "dev": true - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", - "dev": true - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", - "dev": true - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "^0.1.1" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "run-parallel": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true - }, - "run-sequence": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", - "integrity": "sha1-HOZD2jb9jH6n4akynaM/wriJhJU=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "fancy-log": "^1.3.2", - "plugin-error": "^0.1.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } - }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", - "dev": true, - "requires": { - "kind-of": "^1.1.0" - } - }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", - "dev": true - }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", - "dev": true, - "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "rxjs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", - "dev": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", - "dev": true - }, - "seek-bzip": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", - "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.8.1" - } - }, - "select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", - "dev": true, - "requires": { - "sver-compat": "^1.5.0" - } - }, - "semver-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", - "dev": true, - "optional": true - }, - "semver-truncate": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", - "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=", - "dev": true, - "optional": true, - "requires": { - "semver": "^5.3.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "signalr": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/signalr/-/signalr-2.4.0.tgz", - "integrity": "sha512-GPJHb3pcNk3IUui5/WG8lMuarEn+Vpc8wEvJ60w0KQ43W9FHnJcuNcF8dkZePr81eBslzicsRdyEunKNF7KjZQ==", - "requires": { - "jquery": ">=1.6.4" - } - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dev": true, - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "dev": true - } - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "socket.io": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", - "dev": true, - "requires": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", - "dev": true - }, - "socket.io-client": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", - "to-array": "0.1.4" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "socket.io-parser": { - "version": "3.2.0", - "resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "optional": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "sort-keys-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", - "dev": true, - "optional": true, - "requires": { - "sort-keys": "^1.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "spectrum-colorpicker": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/spectrum-colorpicker/-/spectrum-colorpicker-1.8.0.tgz", - "integrity": "sha1-uSbPUALAp3hgtfg1HhwJPGUgAQc=" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "squeak": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/squeak/-/squeak-1.3.0.tgz", - "integrity": "sha1-MwRQN7ZDiLVnZ0uEMiplIQc5FsM=", - "dev": true, - "optional": true, - "requires": { - "chalk": "^1.0.0", - "console-stream": "^0.1.1", - "lpad-align": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "optional": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "optional": true - } - } - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", - "dev": true - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "streamroller": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz", - "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", - "dev": true, - "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" - }, - "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" - } - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - } - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string.prototype.trimleft": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", - "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", - "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "dev": true, - "requires": { - "first-chunk-stream": "^2.0.0", - "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", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", - "dev": true, - "optional": true, - "requires": { - "is-natural-number": "^4.0.1" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "optional": true - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "optional": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", - "dev": true, - "optional": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "dev": true, - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", - "dev": true, - "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "svgo": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz", - "integrity": "sha512-MLfUA6O+qauLDbym+mMZgtXCGRfIxyQoeH6IKVcFslyODEe/ElJNwr0FohQ3xG4C6HK6bk3KYPPXwHVJk3V5NQ==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.33", - "csso": "^3.5.1", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - } - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "dev": true, - "optional": true, - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", - "dev": true, - "optional": true - }, - "tempfile": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", - "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=", - "dev": true, - "optional": true, - "requires": { - "temp-dir": "^1.0.0", - "uuid": "^3.0.1" - } - }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "through2-concurrent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/through2-concurrent/-/through2-concurrent-2.0.0.tgz", - "integrity": "sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==", - "dev": true, - "requires": { - "through2": "^2.0.0" - } - }, - "through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true, - "optional": true - }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "dev": true, - "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", - "dev": true - }, - "tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, - "tinymce": { - "version": "4.9.9", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.9.tgz", - "integrity": "sha512-7Wqh4PGSAWm6FyNwyI1uFAaZyzeQeiwd9Gg2R89SpFIqoMrSzNHIYBqnZnlDm4Bd2DJ0wcC6uJhwFrabIE8puw==" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", - "dev": true, - "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" - } - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true, - "optional": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", - "dev": true, - "requires": { - "through2": "^2.0.3" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true, - "optional": true - }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "dev": true, - "optional": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typeahead.js": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz", - "integrity": "sha1-TmTmcbIjEKhgb0rsgFkkuoSwFbg=", - "requires": { - "jquery": ">=1.7" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", - "dev": true - }, - "unbzip2-stream": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", - "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", - "dev": true, - "optional": true, - "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" - }, - "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - } - }, - "undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", - "dev": true - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", - "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", - "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", - "dev": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "optional": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true, - "optional": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true - }, - "vendors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.3.tgz", - "integrity": "sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - }, - "vinyl-bufferstream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", - "integrity": "sha1-BTeGn1gO/6TKRay0dXnkuf5jCBo=", - "dev": true, - "requires": { - "bufferstreams": "1.0.1" - } - }, - "vinyl-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", - "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.3.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^2.0.0", - "vinyl": "^1.1.0" - }, - "dependencies": { - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } - } - }, - "vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", - "dev": true, - "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", - "dev": true, - "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } - } - }, - "vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", - "dev": true, - "requires": { - "source-map": "^0.5.1" - } - }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "xmlbuilder": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", - "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, - "requires": { - "camelcase": "^3.0.0" - } - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dev": true, - "optional": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - } - } -} diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 8ab5980107..c298f063a7 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -42,7 +42,7 @@ "npm": "6.13.6", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", - "tinymce": "4.9.9", + "tinymce": "4.9.10", "typeahead.js": "0.11.1", "underscore": "1.9.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 9117ab548c..cb5a9c712a 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 @@ -63,10 +63,14 @@ vm.labels = {}; localizationService.localizeMany([ vm.usernameIsEmail ? "general_email" : "general_username", - vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint"] + vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint", + vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername", + "placeholders_emptyPassword"] ).then(function (data) { vm.labels.usernameLabel = data[0]; vm.labels.usernamePlaceholder = data[1]; + vm.labels.usernameError = data[2]; + vm.labels.passwordError = data[3]; }); vm.twoFactor = {}; @@ -193,70 +197,70 @@ } function loginSubmit() { - - // make sure that we are returning to the login view. - vm.view = "login"; - - // TODO: Do validation properly like in the invite password update + + if (formHelper.submitForm({ scope: $scope })) { + //if the login and password are not empty we need to automatically + // validate them - this is because if there are validation errors on the server + // then the user has to change both username & password to resubmit which isn't ideal, + // so if they're not empty, we'll just make sure to set them to valid. + if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); + } + + if (vm.loginForm.$invalid) { + SetTitle(); + return; + } + + // make sure that we are returning to the login view. + vm.view = "login"; - //if the login and password are not empty we need to automatically - // validate them - this is because if there are validation errors on the server - // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty, we'll just make sure to set them to valid. - if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } + vm.loginStates.submitButton = "busy"; - if (vm.loginForm.$invalid) { - return; - } + userService.authenticate(vm.login, vm.password) + .then(function(data) { + vm.loginStates.submitButton = "success"; + userService._retryRequestQueue(true); + if (vm.onLogin) { + vm.onLogin(); + } + }, + function(reason) { - vm.loginStates.submitButton = "busy"; + //is Two Factor required? + if (reason.status === 402) { + vm.errorMsg = "Additional authentication required"; + show2FALoginDialog(reason.data.twoFactorView); + } else { + vm.loginStates.submitButton = "error"; + vm.errorMsg = reason.errorMsg; - userService.authenticate(vm.login, vm.password) - .then(function (data) { - vm.loginStates.submitButton = "success"; - userService._retryRequestQueue(true); - if(vm.onLogin) { - vm.onLogin(); + //set the form inputs to invalid + vm.loginForm.username.$setValidity("auth", false); + vm.loginForm.password.$setValidity("auth", false); + } + + userService._retryRequestQueue(); + + }); + + //setup a watch for both of the model values changing, if they change + // while the form is invalid, then revalidate them so that the form can + // be submitted again. + vm.loginForm.username.$viewChangeListeners.push(function() { + if (vm.loginForm.$invalid) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); } - }, - function (reason) { - - //is Two Factor required? - if (reason.status === 402) { - vm.errorMsg = "Additional authentication required"; - show2FALoginDialog(reason.data.twoFactorView); - } - else { - vm.loginStates.submitButton = "error"; - vm.errorMsg = reason.errorMsg; - - //set the form inputs to invalid - vm.loginForm.username.$setValidity("auth", false); - vm.loginForm.password.$setValidity("auth", false); - } - - userService._retryRequestQueue(); - }); - - //setup a watch for both of the model values changing, if they change - // while the form is invalid, then revalidate them so that the form can - // be submitted again. - vm.loginForm.username.$viewChangeListeners.push(function () { - if (vm.loginForm.$invalid) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } - }); - vm.loginForm.password.$viewChangeListeners.push(function () { - if (vm.loginForm.$invalid) { - vm.loginForm.username.$setValidity('auth', true); - vm.loginForm.password.$setValidity('auth', true); - } - }); + vm.loginForm.password.$viewChangeListeners.push(function() { + if (vm.loginForm.$invalid) { + vm.loginForm.username.$setValidity('auth', true); + vm.loginForm.password.$setValidity('auth', true); + } + }); + } } function requestPasswordResetSubmit(email) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index d674b3d1e8..c07bb9bc83 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -31,7 +31,7 @@ $scope.page.hideActionsMenu = infiniteMode ? true : false; $scope.page.hideChangeVariant = false; $scope.allowOpen = true; - $scope.app = null; + $scope.activeApp = null; //initializes any watches function startWatches(content) { @@ -74,31 +74,23 @@ var isAppPresent = false; // on first init, we dont have any apps. but if we are re-initializing, we do, but ... - if ($scope.app) { + if ($scope.activeApp) { - // 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) { + if (app.alias === $scope.activeApp.alias) { isAppPresent = true; + $scope.appChanged(app); } }); - // 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); - } - }); + // active app does not exist anymore. + $scope.activeApp = null; } - } // if we still dont have a app, lets show the first one: - if (isAppPresent === false && content.apps.length) { - content.apps[0].active = true; + if ($scope.activeApp === null && content.apps.length) { $scope.appChanged(content.apps[0]); } // otherwise make sure the save options are up to date with the current content state @@ -151,8 +143,8 @@ } /** Returns true if the content item varies by culture */ - function isContentCultureVariant() { - return $scope.content.variants.length > 1; + function hasVariants(content) { + return content.variants.length > 1; } function reload() { @@ -215,6 +207,13 @@ })); } + function appendRuntimeData() { + $scope.content.variants.forEach((variant) => { + variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); + variant.htmlId = "_content_variant_" + variant.compositeId + "_"; + }); + } + /** * This does the content loading and initializes everything, called on first load */ @@ -226,6 +225,7 @@ $scope.content = data; + appendRuntimeData(); init(); syncTreeNode($scope.content, $scope.content.path, true); @@ -251,6 +251,7 @@ $scope.content = data; + appendRuntimeData(); init(); startWatches($scope.content); @@ -274,7 +275,7 @@ $scope.page.saveButtonStyle = content.trashed || content.isElement || content.isBlueprint ? "primary" : "info"; // only create the save/publish/preview buttons if the // content app is "Conent" - if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo" && $scope.app.alias !== "umbListView") { + if ($scope.activeApp && $scope.activeApp.alias !== "umbContent" && $scope.activeApp.alias !== "umbInfo" && $scope.activeApp.alias !== "umbListView") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -589,7 +590,7 @@ $scope.sendToPublish = function () { clearNotifications($scope.content); - if (isContentCultureVariant()) { + if (hasVariants($scope.content)) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { @@ -649,7 +650,7 @@ $scope.saveAndPublish = function () { clearNotifications($scope.content); - if (isContentCultureVariant()) { + if (hasVariants($scope.content)) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publish" })) { var dialog = { @@ -711,7 +712,7 @@ $scope.save = function () { clearNotifications($scope.content); // TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant - if (isContentCultureVariant()) { + if (hasVariants($scope.content)) { //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) { @@ -776,7 +777,7 @@ clearNotifications($scope.content); //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "schedule" })) { - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { //ensure the flags are set $scope.content.variants[0].save = true; } @@ -813,7 +814,7 @@ }, function (err) { clearDirtyState($scope.content.variants); //if this is invariant, show the notification errors, else they'll be shown inline with the variant - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { formHelper.showNotifications(err.data); } model.submitButtonState = "error"; @@ -840,7 +841,7 @@ //before we launch the dialog we want to execute all client side validations first if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) { - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { //ensure the flags are set $scope.content.variants[0].save = true; $scope.content.variants[0].publish = true; @@ -873,7 +874,7 @@ }, function (err) { clearDirtyState($scope.content.variants); //if this is invariant, show the notification errors, else they'll be shown inline with the variant - if (!isContentCultureVariant()) { + if (!hasVariants($scope.content)) { formHelper.showNotifications(err.data); } model.submitButtonState = "error"; @@ -963,11 +964,18 @@ * Call back when a content app changes * @param {any} app */ - $scope.appChanged = function (app) { + $scope.appChanged = function (activeApp) { - $scope.app = app; + $scope.activeApp = activeApp; + + _.forEach($scope.content.apps, function (app) { + app.active = false; + if (app.alias === $scope.activeApp.alias) { + app.active = true; + } + }); - $scope.$broadcast("editors.apps.appChanged", { app: app }); + $scope.$broadcast("editors.apps.appChanged", { app: activeApp }); createButtons($scope.content); @@ -1029,6 +1037,7 @@ getMethod: "&", getScaffoldMethod: "&?", culture: "=?", + segment: "=?", infiniteModel: "=?" } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 95b2a520d1..1e929af6e9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -323,7 +323,7 @@ // find the urls for the currently selected language if (scope.node.variants.length > 1) { // nodes with variants - scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture); + scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture)); } else { // invariant nodes scope.currentUrls = scope.node.urls; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js index 06f426889f..3aa0470262 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js @@ -4,7 +4,7 @@ /** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */ function tabbedContentDirective($timeout) { - function link($scope, $element, $attrs) { + function link($scope, $element) { var appRootNode = $element[0]; @@ -115,21 +115,18 @@ } - function controller($scope, $element, $attrs) { - + function controller($scope) { //expose the property/methods for other directives to use this.content = $scope.content; - this.activeVariant = _.find(this.content.variants, variant => { - return variant.active; - }); - - $scope.activeVariant = this.activeVariant; - - $scope.defaultVariant = _.find(this.content.variants, variant => { - return variant.language.isDefault; - }); - + + if($scope.contentNodeModel) { + $scope.defaultVariant = _.find($scope.contentNodeModel.variants, variant => { + // defaultVariant will never have segment. Wether it has a language or not depends on the setup. + return !variant.segment && ((variant.language && variant.language.isDefault) || (!variant.language)); + }); + } + $scope.unlockInvariantValue = function(property) { property.unlockInvariantValue = !property.unlockInvariantValue; }; @@ -141,6 +138,24 @@ } } ); + + $scope.propertyEditorDisabled = function (property) { + if (property.unlockInvariantValue) { + return false; + } + + var contentLanguage = $scope.content.language; + + var canEditCulture = !contentLanguage || + // If the property culture equals the content culture it can be edited + property.culture === contentLanguage.culture || + // A culture-invariant property can only be edited by the default language variant + (property.culture == null && contentLanguage.isDefault); + + var canEditSegment = property.segment === $scope.content.segment; + + return !canEditCulture || !canEditSegment; + } } var directive = { @@ -150,7 +165,8 @@ controller: controller, link: link, scope: { - content: "=" + content: "=", // in this context the content is the variant model. + contentNodeModel: "=?" //contentNodeModel is the content model for the node, } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 6fec20b256..2d3a8e2238 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -12,7 +12,6 @@ editor: "<", editorIndex: "<", editorCount: "<", - openVariants: "<", onCloseSplitView: "&", onSelectVariant: "&", onOpenSplitView: "&", @@ -25,7 +24,7 @@ controller: umbVariantContentController }; - function umbVariantContentController($scope, $element, $location) { + function umbVariantContentController($scope) { var unsubscribe = []; @@ -42,13 +41,14 @@ vm.showBackButton = showBackButton; function onInit() { - // disable the name field if the active content app is not "Content" - vm.nameDisabled = false; - angular.forEach(vm.editor.content.apps, function(app){ - if(app.active && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") { - vm.nameDisabled = true; - } - }); + + // Make copy of apps, so we can have a variant specific model for the App. (needed for validation etc.) + vm.editor.variantApps = Utilities.copy(vm.content.apps); + + var activeApp = vm.content.apps.find((app) => app.active); + + onAppChanged(activeApp); + } function showBackButton() { @@ -94,14 +94,23 @@ } $scope.$on("editors.apps.appChanged", function($event, $args) { - var app = $args.app; - // disable the name field if the active content app is not "Content" or "Info" - vm.nameDisabled = false; - if(app && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") { - vm.nameDisabled = true; - } + var activeApp = $args.app; + + // sync varaintApps active with new active. + _.forEach(vm.editor.variantApps, function (app) { + app.active = (app.alias === activeApp.alias); + }); + + onAppChanged(activeApp); }); + function onAppChanged(activeApp) { + + // disable the name field if the active content app is not "Content" or "Info" + vm.nameDisabled = (activeApp && activeApp.alias !== "umbContent" && activeApp.alias !== "umbInfo" && activeApp.alias !== "umbListView"); + + } + /** * Used to proxy a callback * @param {any} item diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index 6aba1e6758..a188a83d83 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -8,8 +8,9 @@ templateUrl: 'views/components/content/umb-variant-content-editors.html', bindings: { page: "<", - content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant + content: "<", culture: "<", + segment: "<", onSelectApp: "&?", onSelectAppAnchor: "&?", onBack: "&?", @@ -19,12 +20,11 @@ controller: umbVariantContentEditorsController }; - function umbVariantContentEditorsController($scope, $location, $timeout) { + function umbVariantContentEditorsController($scope, $location, contentEditingHelper) { var prevContentDateUpdated = null; var vm = this; - var activeAppAlias = null; vm.$onInit = onInit; vm.$onChanges = onChanges; @@ -39,13 +39,11 @@ //Used to track how many content views there are (for split view there will be 2, it could support more in theory) vm.editors = []; - //Used to track the open variants across the split views - vm.openVariants = []; /** Called when the component initializes */ function onInit() { prevContentDateUpdated = Utilities.copy(vm.content.updateDate); - setActiveCulture(); + setActiveVariant(); } /** Called when the component has linked all elements, this is when the form controller is available */ @@ -60,14 +58,16 @@ function onChanges(changes) { if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) { - setActiveCulture(); + setActiveVariant(); + } else if (changes.segment && !changes.segment.isFirstChange() && changes.segment.currentValue !== changes.segment.previousValue) { + setActiveVariant(); } } /** Allows us to deep watch whatever we want - executes on every digest cycle */ function doCheck() { if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) { - setActiveCulture(); + setActiveVariant(); prevContentDateUpdated = Utilities.copy(vm.content.updateDate); } } @@ -79,37 +79,32 @@ } /** - * Set the active variant based on the current culture (query string) + * Set the active variant based on the current culture or segment (query string) */ - function setActiveCulture() { + function setActiveVariant() { // set the active variant var activeVariant = null; _.each(vm.content.variants, function (v) { - if (v.language && v.language.culture === vm.culture) { - v.active = true; + if ((vm.culture === "invariant" || v.language && v.language.culture === vm.culture) && v.segment === vm.segment) { activeVariant = v; } - else { - v.active = false; - } }); if (!activeVariant) { // Set the first variant to active if we can't find it. // If the content item is invariant, then only one item exists in the array. - vm.content.variants[0].active = true; activeVariant = vm.content.variants[0]; } - insertVariantEditor(0, initVariant(activeVariant, 0)); + insertVariantEditor(0, activeVariant); if (vm.editors.length > 1) { //now re-sync any other editor content (i.e. if split view is open) for (var s = 1; s < vm.editors.length; s++) { //get the variant from the scope model var variant = _.find(vm.content.variants, function (v) { - return v.language.culture === vm.editors[s].content.language.culture; + return (!v.language || v.language.culture === vm.editors[s].content.language.culture) && v.segment === vm.editors[s].content.segment; }); - vm.editors[s].content = initVariant(variant, s); + vm.editors[s].content = variant; } } @@ -122,157 +117,84 @@ */ function insertVariantEditor(index, variant) { + if (vm.editors[index]) { + if (vm.editors[index].content === variant) { + // This variant is already the content of the editor in this index. + return; + } + vm.editors[index].content.active = false; + } + variant.active = true; + var variantCulture = variant.language ? variant.language.culture : "invariant"; + var variantSegment = variant.segment; - //check if the culture at the index is the same, if it's null an editor will be added - var currentCulture = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].culture; + var currentCulture = index < vm.editors.length ? vm.editors[index].culture : null; + var currentSegment = index < vm.editors.length ? vm.editors[index].segment : null; + + // if index not already exists or if the culture or segment isnt identical then we do a replacement. + if (index >= vm.editors.length || currentCulture !== variantCulture || currentSegment !== variantSegment) { - if (currentCulture !== variantCulture) { - //Not the current culture which means we need to modify the array. + //Not the current culture or segment which means we need to modify the array. //NOTE: It is not good enough to just replace the `content` object at a given index in the array // since that would mean that directives are not re-initialized. vm.editors.splice(index, 1, { + compositeId: variant.compositeId, content: variant, - //used for "track-by" ng-repeat - culture: variantCulture + culture: variantCulture, + segment: variantSegment }); } else { - //replace the editor for the same culture + //replace the content of the editor, since the culture and segment is the same. vm.editors[index].content = variant; } + } - - function initVariant(variant, editorIndex) { - //The model that is assigned to the editor contains the current content variant along - //with a copy of the contentApps. This is required because each editor renders it's own - //header and content apps section and the content apps contains the view for editing content itself - //and we need to assign a view model to the subView so that it is scoped to the current - //editor so that split views work. - - //copy the apps from the main model if not assigned yet to the variant - if (!variant.apps) { - variant.apps = Utilities.copy(vm.content.apps); - } - - //if this is a variant has a culture/language than we need to assign the language drop down info - if (variant.language) { - //if the variant list that defines the header drop down isn't assigned to the variant then assign it now - if (!variant.variants) { - variant.variants = _.map(vm.content.variants, - function (v) { - return _.pick(v, "active", "language", "state"); - }); - } - else { - //merge the scope variants on top of the header variants collection (handy when needing to refresh) - angular.extend(variant.variants, - _.map(vm.content.variants, - function (v) { - return _.pick(v, "active", "language", "state"); - })); - } - - //ensure the current culture is set as the active one - for (var i = 0; i < variant.variants.length; i++) { - if (variant.variants[i].language.culture === variant.language.culture) { - variant.variants[i].active = true; - } - else { - variant.variants[i].active = false; - } - } - - // keep track of the open variants across the different split views - // push the first variant then update the variant index based on the editor index - if (vm.openVariants && vm.openVariants.length === 0) { - vm.openVariants.push(variant.language.culture); - } else { - vm.openVariants[editorIndex] = variant.language.culture; - } - - } - - //then assign the variant to a view model to the content app - var contentApp = _.find(variant.apps, function (a) { - return a.alias === "umbContent"; - }); - - if (contentApp) { - //The view model for the content app is simply the index of the variant being edited - var variantIndex = vm.content.variants.indexOf(variant); - contentApp.viewModel = variantIndex; - } - - // make sure the same app it set to active in the new variant - if (activeAppAlias) { - angular.forEach(variant.apps, function (app) { - app.active = false; - if (app.alias === activeAppAlias) { - app.active = true; - } - }); - } - - return variant; - } + /** * Adds a new editor to the editors array to show content in a split view * @param {any} selectedVariant */ function openSplitView(selectedVariant) { - var selectedCulture = selectedVariant.language.culture; + // enforce content contentApp in splitview. + var contentApp = vm.content.apps.find((app) => app.alias === "umbContent"); + if(contentApp) { + selectApp(contentApp); + } + + insertVariantEditor(vm.editors.length, selectedVariant); + + splitViewChanged(); + + } + + $scope.$on("editors.content.splitViewRequest", function(event, args) {requestSplitView(args);}); + vm.requestSplitView = requestSplitView; + function requestSplitView(args) { + var culture = args.culture; + var segment = args.segment; - //Find the whole variant model based on the culture that was chosen var variant = _.find(vm.content.variants, function (v) { - return v.language.culture === selectedCulture; + return (!v.language || v.language.culture === culture) && v.segment === segment; }); - insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length)); - - //only the content app can be selected since no other apps are shown, and because we copy all of these apps - //to the "editors" we need to update this across all editors - for (var e = 0; e < vm.editors.length; e++) { - var editor = vm.editors[e]; - for (var i = 0; i < editor.content.apps.length; i++) { - var app = editor.content.apps[i]; - if (app.alias === "umbContent") { - app.active = true; - // tell the world that the app has changed (but do it only once) - if (e === 0) { - selectApp(app); - } - } - else { - app.active = false; - } - } + if (variant != null) { + openSplitView(variant); } - - // TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular - editor.collapsed = true; - editor.loading = true; - $timeout(function () { - editor.collapsed = false; - editor.loading = false; - splitViewChanged(); - }, 100); } /** Closes the split view */ function closeSplitView(editorIndex) { // TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular var editor = vm.editors[editorIndex]; - editor.loading = true; - editor.collapsed = true; - $timeout(function () { - vm.editors.splice(editorIndex, 1); - //remove variant from open variants - vm.openVariants.splice(editorIndex, 1); - //update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant) - $location.search("cculture", vm.openVariants[0]); - splitViewChanged(); - }, 400); + vm.editors.splice(editorIndex, 1); + editor.content.active = false; + + //update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant) + + $location.search({"cculture": vm.editors[0].content.language ? vm.editors[0].content.language.culture : null, "csegment": vm.editors[0].content.segment}); + splitViewChanged(); } /** @@ -282,38 +204,26 @@ */ function selectVariant(variant, editorIndex) { - // prevent variants already open in a split view to be opened - if (vm.openVariants.indexOf(variant.language.culture) !== -1) { + var variantCulture = variant.language ? variant.language.culture : "invariant"; + var variantSegment = variant.segment || null; + + // Check if we already have this editor open, if so, do nothing. + if (vm.editors.find((editor) => (!editor.content.language || editor.content.language.culture === variantCulture) && editor.content.segment === variantSegment)) { return; } - + //if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part //of a 2nd split view editor then update the model directly. if (editorIndex === 0) { //If we've made it this far, then update the query string. //The editor will respond to this query string changing. - $location.search("cculture", variant.language.culture); + $location.search("cculture", variantCulture).search("csegment", variantSegment); } else { - //Update the 'active' variant for this editor - var editor = vm.editors[editorIndex]; - //set all variant drop down items as inactive for this editor and then set the selected one as active - for (var i = 0; i < editor.content.variants.length; i++) { - editor.content.variants[i].active = false; - } - variant.active = true; - - //get the variant content model and initialize the editor with that - var contentVariant = _.find(vm.content.variants, - function (v) { - return v.language.culture === variant.language.culture; - }); - editor.content = initVariant(contentVariant, editorIndex); - //update the editors collection - insertVariantEditor(editorIndex, contentVariant); - + insertVariantEditor(editorIndex, variant); + } } @@ -322,25 +232,17 @@ * @param {any} app This is the model of the selected app */ function selectApp(app) { - if (vm.onSelectApp) { - vm.onSelectApp({ "app": app }); + if(vm.onSelectApp) { + vm.onSelectApp({"app": app}); } } - + function selectAppAnchor(app, anchor) { - if (vm.onSelectAppAnchor) { - vm.onSelectAppAnchor({ "app": app, "anchor": anchor }); + if(vm.onSelectAppAnchor) { + vm.onSelectAppAnchor({"app": app, "anchor": anchor}); } } - - $scope.$on("editors.apps.appChanged", function ($event, $args) { - var app = $args.app; - if (app && app.alias) { - activeAppAlias = app.alias; - } - }); - } angular.module('umbraco.directives').component('umbVariantContentEditors', umbVariantContentEditors); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js index 7755d9d63b..f80b3ceb3e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbbreadcrumbs.directive.js @@ -73,7 +73,7 @@ Use this directive to generate a list of breadcrumbs. var path = scope.pathTo(ancestor); $location.path(path); - navigationService.clearSearch(["cculture"]); + navigationService.clearSearch(["cculture", "csegment"]); } scope.pathTo = function (ancestor) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index fe2a6aa40a..7bd812e321 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -2,11 +2,10 @@ 'use strict'; function EditorContentHeader(serverValidationManager, localizationService, editorState) { + function link(scope) { - - function link(scope, el, attr, ctrl) { var unsubscribe = []; - + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -14,19 +13,20 @@ scope.serverValidationAliasField = "Alias"; } - scope.isNew = scope.content.state == "NotCreated"; - - localizationService.localizeMany([ - scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit", - "visuallyHiddenTexts_name", - scope.isNew ? "general_new" : "general_edit"] - ).then(function (data) { + scope.isNew = scope.editor.content.state == "NotCreated"; + localizationService.localizeMany( + [ + scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit", + "placeholders_a11yName", + scope.isNew ? "general_new" : "general_edit" + ] + ).then(function (data) { scope.a11yMessage = data[0]; scope.a11yName = data[1]; var title = data[2] + ": "; if (!scope.isNew) { - scope.a11yMessage += " " + scope.content.name; + scope.a11yMessage += " " + scope.editor.content.name; title += scope.content.name; } else { var name = editorState.current.contentTypeName; @@ -34,199 +34,212 @@ scope.a11yName = name + " " + scope.a11yName; title += name; } - + scope.$emit("$changeTitle", title); }); scope.vm = {}; + scope.vm.hasVariants = false; + scope.vm.hasSubVariants = false; + scope.vm.hasCulture = false; + scope.vm.hasSegments = false; scope.vm.dropdownOpen = false; - scope.vm.currentVariant = ""; scope.vm.variantsWithError = []; scope.vm.defaultVariant = null; - scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors. + + function updateVaraintErrors() { + scope.content.variants.forEach( function (variant) { + variant.hasError = scope.variantHasError(variant); + + }); + checkErrorsOnOtherVariants(); + } function checkErrorsOnOtherVariants() { var check = false; - angular.forEach(scope.content.variants, function (variant) { - if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) { + scope.content.variants.forEach( function (variant) { + if (variant.active !== true && variant.hasError) { check = true; } }); scope.vm.errorsOnOtherVariants = check; } + + function onVariantValidation(valid, errors, allErrors, culture, segment) { - function onCultureValidation(valid, errors, allErrors, culture) { - var index = scope.vm.variantsWithError.indexOf(culture); - if (valid === true) { + // only want to react to property errors: + if(errors.findIndex(error => {return error.propertyAlias !== null;}) === -1) { + // we dont have any errors for properties, meaning we will back out. + return; + } + + // If error coming back is invariant, we will assign the error to the default variant by picking the defaultVariant language. + if(culture === "invariant") { + culture = scope.vm.defaultVariant.language.culture; + } + + var index = scope.vm.variantsWithError.findIndex((item) => item.culture === culture && item.segment === segment) + if(valid === true) { if (index !== -1) { scope.vm.variantsWithError.splice(index, 1); } } else { if (index === -1) { - scope.vm.variantsWithError.push(culture); + scope.vm.variantsWithError.push({"culture": culture, "segment": segment}); } } - checkErrorsOnOtherVariants(); + scope.$evalAsync(updateVaraintErrors); } - + function onInit() { - - // find default. - angular.forEach(scope.content.variants, function (variant) { - if (variant.language.isDefault) { + + // find default + check if we have variants. + scope.content.variants.forEach( function (variant) { + if (variant.language !== null && variant.language.isDefault) { scope.vm.defaultVariant = variant; } - }); - - setCurrentVariant(); - - angular.forEach(scope.content.apps, (app) => { - if (app.alias === "umbContent") { - app.anchors = scope.content.tabs; + if (variant.language !== null) { + scope.vm.hasCulture = true; + } + if (variant.segment !== null) { + scope.vm.hasSegments = true; } }); + scope.vm.hasVariants = (scope.vm.hasCulture || scope.vm.hasSegments); + scope.vm.hasSubVariants = (scope.vm.hasCulture && scope.vm.hasSegments); - angular.forEach(scope.content.variants, function (variant) { - unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation)); - }); + updateVaraintErrors(); - unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation)); - - - - } - - function setCurrentVariant() { - angular.forEach(scope.content.variants, function (variant) { - if (variant.active) { - scope.vm.currentVariant = variant; - checkErrorsOnOtherVariants(); + scope.vm.variantMenu = []; + if (scope.vm.hasCulture) { + scope.content.variants.forEach( (v) => { + if (v.language !== null && v.segment === null) { + var variantMenuEntry = { + key: String.CreateGuid(), + open: v.language && v.language.culture === scope.editor.culture, + variant: v, + subVariants: scope.content.variants.filter( (subVariant) => subVariant.language.culture === v.language.culture && subVariant.segment !== null) + }; + scope.vm.variantMenu.push(variantMenuEntry); + } + }); + } else { + scope.content.variants.forEach( (v) => { + scope.vm.variantMenu.push({ + key: String.CreateGuid(), + variant: v + }); + }); } - }); - } + + scope.editor.variantApps.forEach( (app) => { + if (app.alias === "umbContent") { + app.anchors = scope.editor.content.tabs; + } + }); - scope.goBack = function () { - if (scope.onBack) { - scope.onBack(); + scope.content.variants.forEach( function (variant) { + + // if we are looking for the variant with default language then we also want to check for invariant variant. + if (variant.language && variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) { + unsubscribe.push(serverValidationManager.subscribe(null, "invariant", null, onVariantValidation, null)); + } + unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, null, onVariantValidation, variant.segment)); + }); + } - }; - scope.selectVariant = function (event, variant) { + scope.goBack = function () { + if (scope.onBack) { + scope.onBack(); + } + }; - if (scope.onSelectVariant) { - scope.vm.dropdownOpen = false; - scope.onSelectVariant({ "variant": variant }); + scope.selectVariant = function (event, variant) { + + if (scope.onSelectVariant) { + scope.vm.dropdownOpen = false; + scope.onSelectVariant({ "variant": variant }); + } + }; + + scope.selectNavigationItem = function(item) { + if(scope.onSelectNavigationItem) { + scope.onSelectNavigationItem({"item": item}); + } } - }; - scope.selectNavigationItem = function (item) { - if (scope.onSelectNavigationItem) { - scope.onSelectNavigationItem({ "item": item }); + scope.selectAnchorItem = function(item, anchor) { + if(scope.onSelectAnchorItem) { + scope.onSelectAnchorItem({"item": item, "anchor": anchor}); + } } - } - scope.selectAnchorItem = function (item, anchor) { - if (scope.onSelectAnchorItem) { - scope.onSelectAnchorItem({ "item": item, "anchor": anchor }); - } - } + scope.closeSplitView = function () { + if (scope.onCloseSplitView) { + scope.onCloseSplitView(); + } + }; - scope.closeSplitView = function () { - if (scope.onCloseSplitView) { - scope.onCloseSplitView(); - } - }; - - scope.openInSplitView = function (event, variant) { - if (scope.onOpenInSplitView) { - scope.vm.dropdownOpen = false; - scope.onOpenInSplitView({ "variant": variant }); - } - }; - - /** - * keep track of open variants - this is used to prevent the same variant to be open in more than one split view - * @param {any} culture - */ - scope.variantIsOpen = function (culture) { - return (scope.openVariants.indexOf(culture) !== -1); - } - - /** - * Check whether a variant has a error, used to display errors in variant switcher. - * @param {any} culture - */ - scope.variantHasError = function (culture) { - // if we are looking for the default language we also want to check for invariant. - if (culture === scope.vm.defaultVariant.language.culture) { - if (scope.vm.variantsWithError.indexOf("invariant") !== -1) { + scope.openInSplitView = function (event, variant) { + if (scope.onOpenInSplitView) { + scope.vm.dropdownOpen = false; + scope.onOpenInSplitView({ "variant": variant }); + } + }; + + /** + * Check whether a variant has a error, used to display errors in variant switcher. + * @param {any} culture + */ + scope.variantHasError = function(variant) { + if(scope.vm.variantsWithError.find((item) => (!variant.language || item.culture === variant.language.culture) && item.segment === variant.segment) !== undefined) { return true; } + return false; } - if (scope.vm.variantsWithError.indexOf(culture) !== -1) { - return true; - } - return false; - } - onInit(); - - //watch for the active culture changing, if it changes, update the current variant - if (scope.content.variants) { - scope.$watch(function () { - for (var i = 0; i < scope.content.variants.length; i++) { - var v = scope.content.variants[i]; - if (v.active) { - return v.language.culture; - } - } - return scope.vm.currentVariant.language.culture; //should never get here - }, function (newValue, oldValue) { - if (newValue !== scope.vm.currentVariant.language.culture) { - setCurrentVariant(); + onInit(); + + scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); } }); } - scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); + + var directive = { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-content-header.html', + scope: { + name: "=", + nameDisabled: " { + Utilities.forEach(keys, (value, key) => { var attr = element.attr(value); if (attr) { 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 9c33b35e82..ad62bcd3db 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,10 +16,15 @@ angular.module("umbraco.directives") replace: true, templateUrl: 'views/components/property/umb-property.html', link: function (scope) { - userService.getCurrentUser().then(function (u) { - var isAdmin = u.userGroups.indexOf('admin') !== -1; - scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null; - }); + + scope.controlLabelTitle = null; + if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if(u.allowedSections.indexOf("settings") !== -1 ? true : false) { + scope.controlLabelTitle = scope.property.alias; + } + }); + } }, //Define a controller for this directive to expose APIs to other directives controller: function ($scope) { 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 762edefbd5..3f53a1e18c 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 @@ -530,6 +530,7 @@ contentType: scope.contentType, contentTypeName: scope.model.name, contentTypeAllowCultureVariant: scope.model.allowCultureVariant, + contentTypeAllowSegmentVariant: scope.model.allowSegmentVariant, view: "views/common/infiniteeditors/propertysettings/propertysettings.html", size: "small", submit: function (model) { @@ -557,6 +558,7 @@ property.isSensitiveData = propertyModel.isSensitiveData; property.isSensitiveValue = propertyModel.isSensitiveValue; property.allowCultureVariant = propertyModel.allowCultureVariant; + property.allowSegmentVariant = propertyModel.allowSegmentVariant; // update existing data types if (model.updateSameDataTypes) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js index 2d3db7c162..c499a8bfc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbkeyboardshortcutsoverview.directive.js @@ -158,9 +158,9 @@ When this combination is hit an overview is opened with shortcuts based on the m }; function onInit() { - scope.model.forEach(shortcutGroup => { + Utilities.forEach(scope.model, shortcutGroup => { - shortcutGroup.shortcuts.forEach(shortcut => { + Utilities.forEach(shortcutGroup.shortcuts, shortcut => { shortcut.platformKeys = []; // get shortcut keys for mac diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js index 19a33a8351..8d223e427e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblightbox.directive.js @@ -12,9 +12,9 @@
1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) { //This property is locked cause its a invariant property shown on a non-default language. //Therefor do not validate this field. return; @@ -70,7 +71,7 @@ function valPropertyMsg(serverValidationManager, localizationService) { //this can be null if no property was assigned if (scope.currentProperty) { //first try to get the error msg from the server collection - var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, ""); + var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "", null); //if there's an error message use it if (err && err.errorMsg) { return err.errorMsg; @@ -221,25 +222,31 @@ function valPropertyMsg(serverValidationManager, localizationService) { // the correct field validation in their property editors. if (scope.currentProperty) { //this can be null if no property was assigned + + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); + } + else { + scope.errorMsg = ""; + //flag that the current validator is valid + formCtrl.$setValidity('valPropertyMsg', true); + stopWatch(); + } + } + unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias, currentCulture, "", - function(isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - startWatch(); - } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - } - })); + serverValidationManagerCallback, + currentSegment + ) + ); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index a0cc7e3033..3fa9220f7b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -24,6 +24,7 @@ function valServer(serverValidationManager) { var currentProperty = umbPropCtrl.property; var currentCulture = currentProperty.culture; + var currentSegment = currentProperty.segment; if (umbVariantCtrl) { //if we are inside of an umbVariantContent directive @@ -31,7 +32,7 @@ function valServer(serverValidationManager) { var currentVariant = umbVariantCtrl.editor.content; // Lets check if we have variants and we are on the default language then ... - if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) { + if (umbVariantCtrl.content.variants.length > 1 && (!currentVariant.language || !currentVariant.language.isDefault) && !currentCulture && !currentSegment && !currentProperty.unlockInvariantValue) { //This property is locked cause its a invariant property shown on a non-default language. //Therefor do not validate this field. return; @@ -75,7 +76,7 @@ function valServer(serverValidationManager) { if (modelCtrl.$invalid) { modelCtrl.$setValidity('valServer', true); //clear the server validation entry - serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName); + serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName, currentSegment); stopWatch(); } }, true); @@ -90,23 +91,26 @@ function valServer(serverValidationManager) { } //subscribe to the server validation changes + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + if (!isValid) { + modelCtrl.$setValidity('valServer', false); + //assign an error msg property to the current validator + modelCtrl.errorMsg = propertyErrors[0].errorMsg; + startWatch(); + } + else { + modelCtrl.$setValidity('valServer', true); + //reset the error message + modelCtrl.errorMsg = ""; + stopWatch(); + } + } unsubscribe.push(serverValidationManager.subscribe(currentProperty.alias, currentCulture, fieldName, - function(isValid, propertyErrors, allErrors) { - if (!isValid) { - modelCtrl.$setValidity('valServer', false); - //assign an error msg property to the current validator - modelCtrl.errorMsg = propertyErrors[0].errorMsg; - startWatch(); - } - else { - modelCtrl.$setValidity('valServer', true); - //reset the error message - modelCtrl.errorMsg = ""; - stopWatch(); - } - })); + serverValidationManagerCallback, + currentSegment) + ); scope.$on('$destroy', function () { stopWatch(); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js index de340bab27..a519f81054 100644 --- a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsJoinArray.filter.js @@ -1,6 +1,6 @@ /** * @ngdoc filter - * @name umbraco.filters.filter:CMS_joinArray + * @name umbraco.filters.filter:umbCmsJoinArray * @namespace umbCmsJoinArray * * param {array} array of string or objects, if an object use the third argument to specify which prop to list. diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsTitleCase.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsTitleCase.filter.js new file mode 100644 index 0000000000..8d2c233655 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/umbCmsTitleCase.filter.js @@ -0,0 +1,19 @@ +/** + * @ngdoc filter + * @name umbraco.filters.filter:umbCmsTitleCase + * @namespace umbCmsTitleCase + * + * param {string} the text turned into title case. + * + * @description + * Transforms text to title case. Capitalizes the first letter of each word, and transforms the rest of the word to lower case. + * + */ +angular.module("umbraco.filters").filter('umbCmsTitleCase', function() { + return function (str) { + return str.replace( + /\w\S*/g, + txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() + ); + } +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js index 186f3accf0..283a2a7ae9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js @@ -24,6 +24,7 @@ if ($routeParams) { // it's an API request, add the current client culture as a header value config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + config.headers["X-UMB-SEGMENT"] = $routeParams.csegment ? $routeParams.csegment : null; } return config; diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js index 6e6eb00da7..f07a2a441c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/_utils.js @@ -90,7 +90,7 @@ angular.module('umbraco.mocks'). name: "1 column layout", sections: [ { - grid: 12, + grid: 12 } ] }, @@ -98,7 +98,7 @@ angular.module('umbraco.mocks'). name: "2 column layout", sections: [ { - grid: 4, + grid: 4 }, { grid: 8 @@ -139,7 +139,7 @@ angular.module('umbraco.mocks'). } } - }, + } ] }, { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js index c3a1ba6432..d7d6ec862c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/clipboard.service.js @@ -12,6 +12,9 @@ */ function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) { + + var clearPropertyResolvers = []; + var STORAGE_KEY = "umbClipboardService"; @@ -53,13 +56,32 @@ function clipboardService(notificationsService, eventsService, localStorageServi return false; } - var prepareEntryForStorage = function(entryData) { - var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data) - delete shallowCloneData.key; - delete shallowCloneData.$$hashKey; - - return shallowCloneData; + function clearPropertyForStorage(prop) { + + for (var i=0; i prepareEntryForStorage(data)); + var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod)); // remove previous copies of this entry: storage.entries = storage.entries.filter( diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 5cf54ad6a0..bfcc0d536e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -328,9 +328,11 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt * * @description * Returns a id for the variant that is unique between all variants on the content + * Note "invariant" is used for the invariant culture, + * "null" is used for the NULL segment */ buildCompositeVariantId: function (variant) { - return (variant.language ? variant.language.culture : "invariant") + "_" + (variant.segment ? variant.segment : ""); + return (variant.language ? variant.language.culture : "invariant") + "_" + (variant.segment ? variant.segment : "null"); }, @@ -698,7 +700,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt // /belle/#/content/edit/9876 (where 9876 is the new id) //clear the query strings - navigationService.clearSearch(["cculture"]); + navigationService.clearSearch(["cculture", "csegment"]); if (softRedirect) { navigationService.setSoftRedirect(); } 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 9e0285d58d..38aee3fc4a 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 @@ -39,18 +39,22 @@ function fileManager($rootScope) { args.culture = null; } + if (!args.segment) { + args.segment = null; + } + var metaData = []; if (Utilities.isArray(args.metaData)) { metaData = args.metaData; } - //this will clear the files for the current property/culture and then add the new ones for the current property + //this will clear the files for the current property/culture/segment and then add the new ones for the current property fileCollection = _.reject(fileCollection, function (item) { - return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture); + return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture) && (!args.segment || args.segment === item.segment); }); for (var i = 0; i < args.files.length; i++) { //save the file object to the files collection - fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData }); + fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, segment: args.segment, metaData: metaData }); } }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 9d84c3c616..90fbd76ec9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -159,9 +159,16 @@ function formHelper(angularHelper, serverValidationManager, notificationsService //the alias in model state can be in dot notation which indicates // * the first part is the content property alias // * the second part is the field to which the valiation msg is associated with - //There will always be at least 3 parts for content properties since all model errors for properties are prefixed with "_Properties" + //There will always be at least 4 parts for content properties since all model errors for properties are prefixed with "_Properties" //If it is not prefixed with "_Properties" that means the error is for a field of the object directly. + // Example: "_Properties.headerImage.en-US.mySegment.myField" + // * it's for a property since it has a _Properties prefix + // * it's for the headerImage property type + // * it's for the en-US culture + // * it's for the mySegment segment + // * it's for the myField html field (optional) + var parts = e.split("."); //Check if this is for content properties - specific to content/media/member editors because those are special @@ -179,16 +186,23 @@ function formHelper(angularHelper, serverValidationManager, notificationsService } } - //if it contains 3 '.' then we will wire it up to a property's html field + var segment = null; if (parts.length > 3) { - //add an error with a reference to the field for which the validation belongs too - serverValidationManager.addPropertyError(propertyAlias, culture, parts[3], modelState[e][0]); + segment = parts[3]; + //special check in case the string is formatted this way + if (segment === "null") { + segment = null; + } } - else { - //add a generic error for the property, no reference to a specific html field - serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]); + + var htmlFieldReference = ""; + if (parts.length > 4) { + htmlFieldReference = parts[4] || ""; } + // add a generic error for the property + serverValidationManager.addPropertyError(propertyAlias, culture, htmlFieldReference, modelState[e][0], segment); + } else { //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 0198907119..da784a1f9e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -29,7 +29,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService //A list of query strings defined that when changed will not cause a reload of the route - var nonRoutingQueryStrings = ["mculture", "cculture", "lq", "sr"]; + var nonRoutingQueryStrings = ["mculture", "cculture", "csegment", "lq", "sr"]; var retainedQueryStrings = ["mculture"]; function setMode(mode) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index e2e51a6c28..718e44d66e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -13,13 +13,14 @@ function serverValidationManager($timeout) { var callbacks = []; /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback, culture) { + function executeCallback(self, errorsForCallback, callback, culture, segment) { callback.apply(self, [ false, // pass in a value indicating it is invalid errorsForCallback, // pass in the errors for this item self.items, // pass in all errors in total - culture // pass the culture that we are listing for. + culture, // pass the culture that we are listing for. + segment // pass the segment that we are listing for. ] ); } @@ -35,7 +36,8 @@ function serverValidationManager($timeout) { }); } - function getPropertyErrors(self, propertyAlias, culture, fieldName) { + + function getPropertyErrors(self, propertyAlias, culture, segment, fieldName) { if (!Utilities.isString(propertyAlias)) { throw "propertyAlias must be a string"; } @@ -46,22 +48,28 @@ function serverValidationManager($timeout) { if (!culture) { culture = "invariant"; } + if (!segment) { + segment = null; + } //find all errors for this property return _.filter(self.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } - function getCultureErrors(self, culture) { + function getVariantErrors(self, culture, segment) { if (!culture) { culture = "invariant"; } + if (!segment) { + segment = null; + } //find all errors for this property return _.filter(self.items, function (item) { - return (item.culture === culture); + return (item.culture === culture && item.segment === segment); }); } @@ -71,21 +79,21 @@ function serverValidationManager($timeout) { //its a field error callback var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName); if (fieldErrors.length > 0) { - executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture); + executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); } } else if (callbacks[cb].propertyAlias != null) { //its a property error - var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName); + var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].segment, callbacks[cb].fieldName); if (propErrors.length > 0) { - executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture); + executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); } } else { - //its a culture error - var cultureErrors = getCultureErrors(self, callbacks[cb].culture); - if (cultureErrors.length > 0) { - executeCallback(self, cultureErrors, callbacks[cb].callback, callbacks[cb].culture); + //its a variant error + var variantErrors = getVariantErrors(self, callbacks[cb].culture, callbacks[cb].segment); + if (variantErrors.length > 0) { + executeCallback(self, variantErrors, callbacks[cb].callback, callbacks[cb].culture, callbacks[cb].segment); } } } @@ -150,20 +158,27 @@ function serverValidationManager($timeout) { * field alias to listen for. * If propertyAlias is null, then this subscription is for a field property (not a user defined property). */ - subscribe: function (propertyAlias, culture, fieldName, callback) { + subscribe: function (propertyAlias, culture, fieldName, callback, segment) { if (!callback) { return; } var id = String.CreateGuid(); + + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } if (propertyAlias === null) { callbacks.push({ propertyAlias: null, culture: culture, + segment: segment, fieldName: fieldName, callback: callback, id: id @@ -175,6 +190,7 @@ function serverValidationManager($timeout) { callbacks.push({ propertyAlias: propertyAlias, culture: culture, + segment: segment, fieldName: fieldName, callback: callback, id: id @@ -199,25 +215,29 @@ function serverValidationManager($timeout) { * @param {} fieldName * @returns {} */ - unsubscribe: function (propertyAlias, culture, fieldName) { + unsubscribe: function (propertyAlias, culture, fieldName, segment) { - //normalize culture to null + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } if (propertyAlias === null) { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName; + return item.propertyAlias === null && item.culture === culture && item.segment === segment && item.fieldName === fieldName; }); } else if (propertyAlias !== undefined) { //remove all callbacks for the content property callbacks = _.reject(callbacks, function (item) { - return item.propertyAlias === propertyAlias && item.culture === culture && + return item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || ((item.fieldName === undefined || item.fieldName === "") && (fieldName === undefined || fieldName === ""))); }); @@ -236,16 +256,20 @@ function serverValidationManager($timeout) { * This will always return any callbacks registered for just the property (i.e. field name is empty) and for ones with an * explicit field name set. */ - getPropertyCallbacks: function (propertyAlias, culture, fieldName) { + getPropertyCallbacks: function (propertyAlias, culture, fieldName, segment) { - //normalize culture to null + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly against the field and for only the property - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); }); return found; }, @@ -262,7 +286,7 @@ function serverValidationManager($timeout) { getFieldCallbacks: function (fieldName) { var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly against the field - return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return found; }, @@ -274,12 +298,29 @@ function serverValidationManager($timeout) { * @function * * @description - * Gets all callbacks that has been registered using the subscribe method for the culture. + * Gets all callbacks that has been registered using the subscribe method for the culture. Not including segments. */ getCultureCallbacks: function (culture) { var found = _.filter(callbacks, function (item) { //returns any callback that have been registered directly/ONLY against the culture - return (item.culture === culture && item.propertyAlias === null && item.fieldName === null); + return (item.culture === culture && item.segment === null && item.propertyAlias === null && item.fieldName === null); + }); + return found; + }, + + /** + * @ngdoc function + * @name getVariantCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the culture and segment. + */ + getVariantCallbacks: function (culture, segment) { + var found = _.filter(callbacks, function (item) { + //returns any callback that have been registered directly against the given culture and given segment. + return (item.culture === culture && item.segment === segment && item.propertyAlias === null && item.fieldName === null); }); return found; }, @@ -303,6 +344,7 @@ function serverValidationManager($timeout) { this.items.push({ propertyAlias: null, culture: "invariant", + segment: null, fieldName: fieldName, errorMsg: errorMsg }); @@ -314,7 +356,7 @@ function serverValidationManager($timeout) { var cbs = this.getFieldCallbacks(fieldName); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, null); + executeCallback(this, errorsForCallback, cbs[cb].callback, null, null); } }, @@ -327,7 +369,7 @@ function serverValidationManager($timeout) { * @description * Adds an error message for the content property */ - addPropertyError: function (propertyAlias, culture, fieldName, errorMsg) { + addPropertyError: function (propertyAlias, culture, fieldName, errorMsg, segment) { if (!propertyAlias) { return; } @@ -336,31 +378,36 @@ function serverValidationManager($timeout) { if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } //only add the item if it doesn't exist - if (!this.hasPropertyError(propertyAlias, culture, fieldName)) { + if (!this.hasPropertyError(propertyAlias, culture, fieldName, segment)) { this.items.push({ propertyAlias: propertyAlias, culture: culture, + segment: segment, fieldName: fieldName, errorMsg: errorMsg }); } //find all errors for this item - var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, fieldName); + var errorsForCallback = getPropertyErrors(this, propertyAlias, culture, segment, fieldName); //we should now call all of the call backs registered for this error - var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName); + var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName, segment); //call each callback for this error for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, culture); + executeCallback(this, errorsForCallback, cbs[cb].callback, culture, segment); } - //execute culture specific callbacks here too when a propery error is added - var cultureCbs = this.getCultureCallbacks(culture); + //execute variant specific callbacks here too when a propery error is added + var variantCbs = this.getVariantCallbacks(culture, segment); //call each callback for this error - for (var cb in cultureCbs) { - executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture); + for (var cb in variantCbs) { + executeCallback(this, errorsForCallback, variantCbs[cb].callback, culture, segment); } }, @@ -373,7 +420,7 @@ function serverValidationManager($timeout) { * @description * Removes an error message for the content property */ - removePropertyError: function (propertyAlias, culture, fieldName) { + removePropertyError: function (propertyAlias, culture, fieldName, segment) { if (!propertyAlias) { return; @@ -383,10 +430,14 @@ function serverValidationManager($timeout) { if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } //remove the item this.items = _.reject(this.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); }, @@ -405,7 +456,10 @@ function serverValidationManager($timeout) { callbacks[cb].callback.apply(this, [ true, //pass in a value indicating it is VALID [], //pass in empty collection - []]); //pass in empty collection + [], + null, + null] + ); } }, @@ -431,16 +485,20 @@ function serverValidationManager($timeout) { * @description * Gets the error message for the content property */ - getPropertyError: function (propertyAlias, culture, fieldName) { + getPropertyError: function (propertyAlias, culture, fieldName, segment) { - //normalize culture to null + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); return err; }, @@ -457,7 +515,7 @@ function serverValidationManager($timeout) { getFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return err; }, @@ -471,16 +529,20 @@ function serverValidationManager($timeout) { * @description * Checks if the content property + culture + field name combo has an error */ - hasPropertyError: function (propertyAlias, culture, fieldName) { + hasPropertyError: function (propertyAlias, culture, fieldName, segment) { //normalize culture to null if (!culture) { culture = "invariant"; } + //normalize segment to null + if (!segment) { + segment = null; + } var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); return err ? true : false; }, @@ -497,12 +559,11 @@ function serverValidationManager($timeout) { hasFieldError: function (fieldName) { var err = _.find(this.items, function (item) { //return true if the property alias matches and if an empty field name is specified or the field name matches - return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); + return (item.propertyAlias === null && item.culture === "invariant" && item.segment === null && item.fieldName === fieldName); }); return err ? true : false; }, - /** * @ngdoc function * @name hasCultureError @@ -513,14 +574,40 @@ function serverValidationManager($timeout) { * Checks if the given culture has an error */ hasCultureError: function (culture) { - - //normalize culture to null + + //normalize culture to "invariant" if (!culture) { culture = "invariant"; } + + var err = _.find(this.items, function (item) { + return (item.culture === culture && item.segment === null); + }); + return err ? true : false; + }, + + /** + * @ngdoc function + * @name hasVariantError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the given culture has an error + */ + hasVariantError: function (culture, segment) { + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } var err = _.find(this.items, function (item) { - return item.culture === culture; + return (item.culture === culture && item.segment === segment); }); return err ? true : false; }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js index a11a9d6c82..4166725c39 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js @@ -19,9 +19,9 @@ function registerAllTours() { tours = []; return tourResource.getTours().then(function (tourFiles) { - tourFiles.forEach(tourFile => { + Utilities.forEach(tourFiles, tourFile => { - tourFile.tours.forEach(newTour => { + Utilities.forEach(tourFile.tours, newTour => { validateTour(newTour); validateTourRegistration(newTour); tours.push(newTour); @@ -251,7 +251,7 @@ */ function validateTourRegistration(tour) { // check for existing tours with the same alias - tours.forEach(existingTour => { + Utilities.forEach(tours, existingTour => { if (existingTour.alias === tour.alias) { throw "A tour with the alias " + tour.alias + " is already registered"; } @@ -267,17 +267,17 @@ var deferred = $q.defer(); currentUserResource.getTours().then(function (storedTours) { - storedTours.forEach(storedTour => { + Utilities.forEach(storedTours, storedTour => { if (storedTour.completed === true) { - tours.forEach(tour => { + Utilities.forEach(tours, tour => { if (storedTour.alias === tour.alias) { tour.completed = true; } }); } if (storedTour.disabled === true) { - tours.forEach(tour => { + Utilities.forEach(tours, tour => { if (storedTour.alias === tour.alias) { tour.disabled = true; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index bae099f26d..11c3db9aee 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -64,7 +64,7 @@ var saveModel = _.pick(displayModel, 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', - 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'isElement'); + 'key', 'parentId', 'alias', 'path', 'allowCultureVariant', 'allowSegmentVariant', 'isElement'); // TODO: Map these saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); @@ -83,7 +83,7 @@ }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile', 'isSensitiveData', 'allowCultureVariant', 'allowSegmentVariant'); return saveProperty; }); @@ -340,6 +340,7 @@ name: v.name || "", //if its null/empty,we must pass up an empty string else we get json converter errors properties: getContentProperties(v.tabs), culture: v.language ? v.language.culture : null, + segment: v.segment, publish: v.publish, save: v.save, releaseDate: v.releaseDate, @@ -366,38 +367,59 @@ */ formatContentGetData: function(displayModel) { - //We need to check for invariant properties among the variant variants. - //When we detect this, we want to make sure that the property object instance is the - //same reference object between all variants instead of a copy (which it will be when - //return from the JSON structure). + // We need to check for invariant properties among the variant variants, + // as the value of an invariant property is shared between different variants. + // A property can be culture invariant, segment invariant, or both. + // When we detect this, we want to make sure that the property object instance is the + // same reference object between all variants instead of a copy (which it will be when + // return from the JSON structure). if (displayModel.variants && displayModel.variants.length > 1) { + // Collect all invariant properties from the variants that are either the + // default language variant or the default segment variant. + var defaultVariants = _.filter(displayModel.variants, function (variant) { + var isDefaultLanguage = variant.language && variant.language.isDefault; + var isDefaultSegment = variant.segment == null; - var invariantProperties = []; - - //collect all invariant properties on the first first variant - var firstVariant = displayModel.variants[0]; - _.each(firstVariant.tabs, function(tab, tabIndex) { - _.each(tab.properties, function (property, propIndex) { - //in theory if there's more than 1 variant, that means they would all have a language - //but we'll do our safety checks anyways here - if (firstVariant.language && !property.culture) { - invariantProperties.push({ - tabIndex: tabIndex, - propIndex: propIndex, - property: property - }); - } - }); + return isDefaultLanguage || isDefaultSegment; }); + if (defaultVariants.length > 0) { + _.each(defaultVariants, function (defaultVariant) { + var invariantProps = []; - //now assign this same invariant property instance to the same index of the other variants property array - for (var j = 1; j < displayModel.variants.length; j++) { - var variant = displayModel.variants[j]; + _.each(defaultVariant.tabs, function (tab, tabIndex) { + _.each(tab.properties, function (property, propIndex) { + // culture == null -> property is culture invariant + // segment == null -> property is *possibly* segment invariant + if (!property.culture || !property.segment) { + invariantProps.push({ + tabIndex: tabIndex, + propIndex: propIndex, + property: property + }); + } + }); + }); - _.each(invariantProperties, function (invProp) { - variant.tabs[invProp.tabIndex].properties[invProp.propIndex] = invProp.property; + var otherVariants = _.filter(displayModel.variants, function (variant) { + return variant !== defaultVariant; + }); + + // now assign this same invariant property instance to the same index of the other variants property array + _.each(otherVariants, function (variant) { + _.each(invariantProps, function (invProp) { + var tab = variant.tabs[invProp.tabIndex]; + var prop = tab.properties[invProp.propIndex]; + + var inheritsCulture = prop.culture === invProp.property.culture && prop.segment == null && invProp.property.segment == null; + var inheritsSegment = prop.segment === invProp.property.segment && !prop.culture; + + if (inheritsCulture || inheritsSegment) { + tab.properties[invProp.propIndex] = invProp.property; + } + }); + }); }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index edf698c8a7..4cbc5e567a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -252,12 +252,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe for (var f in args.files) { //each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key // so we know which property it belongs to on the server side - var fileKey = "file_" + args.files[f].alias + "_" + (args.files[f].culture ? args.files[f].culture : ""); + var file = args.files[f]; + var fileKey = "file_" + file.alias + "_" + (file.culture ? file.culture : "") + "_" + (file.segment ? file.segment : ""); - if (Utilities.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) { - fileKey += ("_" + args.files[f].metaData.join("_")); + if (Utilities.isArray(file.metaData) && file.metaData.length > 0) { + fileKey += ("_" + file.metaData.join("_")); } - formData.append(fileKey, args.files[f].file); + formData.append(fileKey, file.file); } }).then(function (response) { //success callback diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 58ef342f44..82353df744 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -206,7 +206,7 @@ function umbSessionStorage($window) { return { get: function (key) { - return JSON.parse(storage["umb_" + key]); + return Utilities.fromJson(storage["umb_" + key]); }, set: function (key, value) { diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 0921f46aac..f0d7c6f1e1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -107,6 +107,7 @@ @import "components/overlays.less"; @import "components/card.less"; @import "components/editor/umb-editor.less"; +@import "components/editor/umb-variant-switcher.less"; @import "components/umb-sub-views.less"; @import "components/umb-editor-navigation.less"; @import "components/umb-editor-navigation-item.less"; @@ -137,6 +138,7 @@ @import "components/tooltip/umb-tooltip-list.less"; @import "components/overlays/umb-overlay-backdrop.less"; @import "components/overlays/umb-itempicker.less"; +@import "components/overlays/umb-variant-selector-overlay"; @import "components/umb-grid.less"; @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; @@ -225,6 +227,7 @@ @import "dashboards/umbraco-forms.less"; @import "dashboards/examine-management.less"; @import "dashboards/healthcheck.less"; +@import "dashboards/content-templates.less"; @import "dashboards/nucache.less"; @import "typeahead.less"; 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 ed80359833..68854e3c53 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -2,20 +2,28 @@ Library of card related compoents, like the right-hand icon list on the grid "cards" */ -.umb-card{ +.umb-card { position: relative; padding: 5px 10px 5px 10px; 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;} + .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; + } } -.umb-card-thumb{ +.umb-card-thumb { text-align: center; - i{ + i { text-align: center; font-size: 20px; line-height: 40px; @@ -25,18 +33,28 @@ } } -.umb-card-content{ - .item-title{color: @blackLight; font-weight: 400; border: none; font-size: 16px; text-transform: none; margin-bottom: 3px;} - p{color: @gray-3; margin-bottom: 1px;} +.umb-card-content { + .item-title { + color: @blackLight; + font-weight: 400; + border: none; + font-size: 16px; + text-transform: none; + margin-bottom: 3px; + } + p { + color: @gray-3; + margin-bottom: 1px; + } } -.umb-card-actions{ +.umb-card-actions { padding-top: 10px; border-top: @gray-10 1px solid; clear: both; } -.umb-card-icons{ +.umb-card-icons { text-align: center; vertical-align: middle; display: block; @@ -45,7 +63,7 @@ padding: 0; } -.umb-card-icons.vertical{ +.umb-card-icons.vertical { position: absolute; top: 7px; right: 7px; @@ -53,19 +71,19 @@ width: 1px; } -.umb-card-icons li{ +.umb-card-icons li { display: inline-block; margin: 0 2px 0 2px; } -.umb-card-icons.vertical li{ +.umb-card-icons.vertical li { float: right; display: block; margin-bottom: 3px; } //card iocn list -.umb-card-list{ +.umb-card-list { display: block; padding: 0; margin: 0; @@ -81,7 +99,7 @@ //Card icon grid for picking items off a card -.umb-card-grid{ +.umb-card-grid { padding: 0; margin: 0 auto; list-style: none; @@ -101,14 +119,24 @@ width: 100px; } +.umb-card-grid.-six-in-row li { + flex: 0 0 25%; + max-width: 117px; +} + .umb-card-grid.-four-in-row li { flex: 0 0 25%; max-width: 25%; } .umb-card-grid.-three-in-row li { - flex: 0 0 33.33%; - max-width:33.33%; + flex: 0 0 33.333%; + max-width:33.333%; + + i { + font-size: 36px; + line-height: 28px; + } } .umb-card-grid .umb-card-grid-item { @@ -117,7 +145,7 @@ width: 100%; //height: 100%; padding-top: 100%; - border-radius: 3px; + border-radius: @baseBorderRadius * 2; transition: background-color 120ms; > span { @@ -141,6 +169,41 @@ color: @ui-option-type-hover; } +.umb-card-grid .umb-card-grid-item-slot { + position: relative; + display: block; + width: 100%; + padding-top: 100%; + border-radius: @baseBorderRadius * 2; + + box-sizing: border-box; + transition: background-color 120ms; + + &:hover, &:focus { + background-color: @ui-option-hover; + > span { + color:@ui-action-discreet-type-hover; + border-color:@ui-action-discreet-border-hover; + } + } + + > span { + position: absolute; + top: 10px; + bottom: 10px; + left: 10px; + right: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + background-color: transparent; + border:1.5px dashed @ui-action-discreet-border; + border-radius: @baseBorderRadius * 2; + } +} + + .umb-card-grid a { color: @ui-option-type; text-decoration: none; @@ -149,6 +212,7 @@ .umb-card-grid i { font-size: 30px; line-height: 20px; + margin-top: 6px; margin-bottom: 10px; display: block; } @@ -164,7 +228,7 @@ //Round icon-like button - this should be somewhere else -.umb-btn-round{ +.umb-btn-round { padding: 4px 6px 4px 6px; display: inline-block; cursor: pointer; @@ -174,7 +238,8 @@ margin: 2px; } -.umb-btn-round:hover, .umb-btn-round:hover *{ +.umb-btn-round:hover, +.umb-btn-round:hover * { background: @blueDark !important; color: @white !important; border-color: @blueDark !important; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index bc84b0d35e..ac55c6ffb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -162,161 +162,6 @@ a.umb-editor-header__close-split-view:hover { } } -/* variant switcher */ -.umb-variant-switcher__toggle { - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - margin: 1px 1px; - right: 0; - height: 30px; - text-decoration: none !important; - font-size: 13px; - color: @ui-action-discreet-type; - background: transparent; - border: none; - - max-width: 50%; - white-space: nowrap; - - user-select: none; - - span { - text-overflow: ellipsis; - overflow: hidden; - } -} - -button.umb-variant-switcher__toggle { - transition: color 0.2s ease-in-out; - &:hover { - //background-color: @gray-10; - color: @ui-action-discreet-type-hover; - .umb-variant-switcher__expand { - color: @ui-action-discreet-type-hover; - } - } - - &.--error { - &::before { - content: '!'; - position: absolute; - top: -8px; - right: -10px; - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 10px; - text-align: center; - font-weight: bold; - background-color: @errorBackground; - color: @errorText; - } - } -} - -.umb-variant-switcher__expand { - color: @ui-action-discreet-type; - margin-top: 3px; - margin-left: 5px; - margin-right: -5px; - transition: color 0.2s ease-in-out; -} - -.umb-variant-switcher__item { - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid @gray-9; - position: relative; -} - -.umb-variant-switcher__item:last-child { - border-bottom: none; -} - -.umb-variant-switcher__item.--current { - color: @ui-light-active-type; -} -.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper { - border-left: 4px solid @ui-active; -} - -.umb-variant-switcher__item:hover { - outline: none; -} - -.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover { - //background-color: @white !important; - cursor: default; -} - -.umb-variant-switcher__item:hover .umb-variant-switcher__split-view { - display: block; - cursor: pointer; -} - -.umb-variant-switcher__item.--error { - .umb-variant-switcher__name { - color: @red; - &::after { - content: '!'; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - top: -3px; - width: 14px; - height: 14px; - border-radius: 7px; - font-size: 8px; - text-align: center; - font-weight: bold; - background-color: @errorBackground; - color: @errorText; - } - } -} - -.umb-variant-switcher__name-wrapper { - font-size: 14px; - flex: 1; - cursor: pointer; - padding-top: 6px !important; - padding-bottom: 6px !important; - background-color: transparent; - border: none; - border-left: 2px solid transparent; -} - -.umb-variant-switcher__name { - display: block; -} - -.umb-variant-switcher__state { - font-size: 13px; - color: @gray-4; -} - -.umb-variant-switcher__split-view { - font-size: 13px; - display: none; - padding: 16px 20px; - position: absolute; - right: 0; - top: 0; - bottom: 0; - background-color: @white; - - &:hover { - background-color: @ui-option-hover; - color: @ui-option-type-hover; - } -} // container diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less new file mode 100644 index 0000000000..8dbc070856 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-variant-switcher.less @@ -0,0 +1,332 @@ +/* variant switcher */ +.umb-variant-switcher__toggle { + position: relative; + display: flex; + align-items: center; + padding: 0 10px; + margin: 1px 1px; + right: 0; + height: 30px; + text-decoration: none !important; + font-size: 13px; + color: @ui-action-discreet-type; + background: transparent; + border: none; + + max-width: 50%; + white-space: nowrap; + + user-select: none; + + span { + text-overflow: ellipsis; + overflow: hidden; + } +} + +button.umb-variant-switcher__toggle { + transition: color 0.2s ease-in-out; + &:hover { + //background-color: @gray-10; + color: @ui-action-discreet-type-hover; + .umb-variant-switcher__expand { + color: @ui-action-discreet-type-hover; + } + } + + &.--error { + &::before { + content: '!'; + position: absolute; + top: -8px; + right: -10px; + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 10px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } +} + +.umb-variant-switcher__expand { + color: @ui-action-discreet-type; + margin-top: 3px; + margin-left: 5px; + margin-right: -5px; + transition: color 0.2s ease-in-out; +} + + +.umb-variant-switcher { + min-width: 100%; + max-height: 80vh; + overflow-y: auto; + margin-top: 5px; + user-select: none; +} + +.umb-variant-switcher__item { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid @gray-9; + position: relative; + .umb-variant-switcher__name-wrapper:hover { + .umb-variant-switcher__name { + color: @blueMid; + } + .umb-variant-switcher__state { + color: @blueMid; + } + } +} +.umb-variant-switcher__item.--state-notCreated:not(.--active) { + .umb-variant-switcher__name-wrapper::before { + content: "+"; + display: block; + float: left; + font-size: 15px; + font-weight: 900; + padding: 8px 16px 8px 6px; + color: @gray-5; + } + .umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper::before { + padding: 8px 16px 8px 20px; + } + .umb-variant-switcher__name { + color: @gray-5; + } + .umb-variant-switcher__state { + color: @gray-6; + } + .umb-variant-switcher__name-wrapper::after { + content: ""; + position: absolute; + z-index: 1; + border: 1px dashed @gray-9; + top: 7px; + bottom: 7px; + left: 7px; + right: 7px; + border-radius: 3px; + pointer-events: none; + } + + .umb-variant-switcher__name-wrapper:hover { + &::before { + color: @blueMid; + } + .umb-variant-switcher__name { + color: @blueMid; + } + .umb-variant-switcher__state { + color: @blueMid; + } + } +} +/* +.umb-variant-switcher__item.--state-draft { + .umb-variant-switcher__name { + color: @gray-5; + } + &:hover { + .umb-variant-switcher__name { + color: @blueMid; + } + } +} +*/ + +.umb-variant-switcher.--has-sub-variants { + .umb-variant-switcher__item { + + } +} + +.umb-variant-switcher__item-expand-button { + text-decoration: none; + display: inline-block; + flex: 0; + align-self: stretch; + + padding-left: 22px !important; + padding-right: 14px !important; + + font-size: 12px; + + * { + pointer-events: none; + } +} + +.umb-variant-switcher__item:last-child { + border-bottom: none; +} + +.umb-variant-switcher__item.--current { + //color: @ui-light-active-type; + //background-color: @pinkExtraLight; + .umb-variant-switcher__name { + //color: @ui-light-active-type; + font-weight: 700; + } + &::before { + content: ''; + position: absolute; + border-radius: 0 4px 4px 0; + background-color: @ui-active-border; + width: 4px; + top:8px; + bottom: 8px; + left:0; + z-index:1; + pointer-events: none; + } +} + +.umb-variant-switcher__item:hover { + outline: none; +} + +.umb-variant-switcher__item.--active:not(.--current) .umb-variant-switcher__name-wrapper:hover { + //background-color: @white !important; + cursor: default; +} + +.umb-variant-switcher__item:focus .umb-variant-switcher__split-view, +.umb-variant-switcher__item:focus-within .umb-variant-switcher__split-view, +.umb-variant-switcher__item:hover .umb-variant-switcher__split-view, +.umb-variant-switcher__split-view:focus { + display: block; + cursor: pointer; +} + +.umb-variant-switcher__item.--error { + .umb-variant-switcher__name { + color: @red; + &::after { + content: '!'; + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + top: -3px; + width: 14px; + height: 14px; + border-radius: 7px; + font-size: 8px; + text-align: center; + font-weight: bold; + background-color: @errorBackground; + color: @errorText; + } + } +} + +.umb-variant-switcher__name-wrapper { + font-size: 14px; + text-align: left; + flex: 1; + cursor: pointer; + background-color: transparent; + border: none; +} +.dropdown-menu>li { + > .umb-variant-switcher__name-wrapper { + padding-top: 10px; + padding-bottom: 10px; + } + + > .umb-variant-switcher__item-expand-button + .umb-variant-switcher__name-wrapper { + padding-left: 5px; + } +} + + +.umb-variant-switcher__name { + display: block; + font-weight: 600; + margin-bottom: -2px; +} + +.umb-variant-switcher__state { + font-size: 12px; + color: @gray-4; +} + +.umb-variant-switcher__split-view { + font-size: 12px; + display: none; + padding: 20px 20px; + position: absolute; + right: 0; + top: 0; + bottom: 0; + background-color: @white; + + &:hover { + background-color: @ui-option-hover; + color: @ui-option-type-hover; + } +} + + +.umb-variant-switcher__sub-variants { + + position: relative; + border-bottom: 1px solid @gray-9; + background-color: @gray-13; + /* + &::before { + content: ""; + position: absolute; + z-index: 1; + top: 0px; + left: 20px; + width: 4px; + bottom: 14px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + background-color: @gray-8; + } + */ + .umb-variant-switcher__item { + border-bottom-color: @gray-10; + } + + .umb-variant-switcher__item.--state-notCreated:not(.--active) { + .umb-variant-switcher__name-wrapper::after { + left: 55px;// overwrite left to achieve same indentation on the dashed border as language. + } + } + + .umb-variant-switcher__name-wrapper { + + margin-left: 48px; + padding-left: 20px; + + padding-top: 10px; + padding-bottom: 10px; + + &:hover { + color: @ui-option-type-hover; + background-color: @ui-option-hover; + } + + .umb-variant-switcher__name { + //margin-right: 20px; + } + .umb-variant-switcher__state { + //flex: 0 0 200px; + } + + + } +} 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 f4402dec7b..381157c8bc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -24,16 +24,19 @@ margin-top: 0; flex-grow: 0; flex-shrink: 0; - padding: 20px 20px 0; + padding: 30px 30px 0; } .umb-overlay__section-header { width: 100%; margin-top:30px; - margin-bottom: 10px; + margin-bottom: 20px; h5 { display: inline; + font-size: 16px; + line-height: 16px; + font-weight: bold; } button { @@ -66,7 +69,7 @@ flex-shrink: 1; flex-basis: auto; position: relative; - padding: 30px; + padding: 20px 30px; background: @white; max-height: calc(100vh - 170px); overflow-y: auto; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-itempicker.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-itempicker.less index 3727c92251..010ff4636d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-itempicker.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-itempicker.less @@ -1,6 +1,3 @@ .umb-itempicker .form-search { margin-top:10px; } -.umb-card-grid { - margin-top: 10px; -} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-variant-selector-overlay.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-variant-selector-overlay.less new file mode 100644 index 0000000000..b50a622f98 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-variant-selector-overlay.less @@ -0,0 +1,26 @@ +.umb-variant-selector-overlay { + + + .umb-variant-selector-entry { + .umb-form-check { + .umb-form-check__symbol { + margin-top: 2px; + } + } + } + .umb-variant-selector-entry__title { + font-weight: 600; + font-size: 14px; + .__secondarytitle { + font-weight: normal; + color: @gray-5; + } + } + .umb-variant-selector-entry__description { + display: block; + font-size: 12px; + color: @gray-4; + } + + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less index 2cca776614..f3c41dbc33 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-editor-navigation-item.less @@ -178,11 +178,11 @@ } // Validation -.umb-sub-views-nav-item__action.-has-error, +.show-validation .umb-sub-views-nav-item__action.-has-error, .show-validation .umb-sub-views-nav-item > a.-has-error { color: @red; - &::after { + &::before { background-color: @red; } } 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 a52f81b92a..9a3760444d 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 @@ -18,6 +18,8 @@ label.umb-form-check--checkbox{ } .umb-form-check__info { margin-left:20px; + position: relative; + top: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index f9d8772d45..ac7a2c63ce 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -23,6 +23,7 @@ } .umb-group-builder__group.-placeholder { + width:100%; min-height: 86px; display: flex; justify-content: center; @@ -136,9 +137,10 @@ input.umb-group-builder__group-title-input:disabled:hover { } .umb-group-builder__group-add-property { - min-height: 46px; - margin-right: 45px; + + width: calc(100% - 315px); margin-left: 270px; + min-height: 46px; border-radius: 3px; display: flex; @@ -344,8 +346,9 @@ input.umb-group-builder__group-title-input:disabled:hover { .umb-group-builder__property-actions { flex: 0 0 44px; - text-align: right; - margin-top: 7px; + display: flex; + align-items: center; + justify-content: flex-end; } .umb-group-builder__property-action { @@ -473,7 +476,7 @@ input.umb-group-builder__group-sort-value { font-weight: bold; resize: none; line-height: 1.5em; - padding-left: 0; + padding: 0; border: none; &:focus { @@ -498,45 +501,82 @@ input.umb-group-builder__group-sort-value { text-decoration: none; color: @ui-action-type-hover; border-color: @ui-action-border-hover; + background-color: @ui-action-discreet-hover; } } - .editor { + .editor-wrapper { margin-bottom: 10px; + } - .editor-icon-wrapper { - border: 1px solid @gray-8; - width: 60px; - height: 60px; - text-align: center; - line-height: 60px; - border-radius: 5px; - float: left; - margin-right: 20px; + .editor { + display: flex; + align-items: center; + align-content: stretch; - .icon { - font-size: 26px; - } + min-height: 80px; + + border: 1px solid @gray-9; + color: @ui-action-discreet-type; + border-radius: @baseBorderRadius; + + } + + .editor-info { + flex: 1 0 auto; + text-align: left; + display: flex; + align-items: center; + max-width: calc(100% - 48px); + min-height: 80px; + color: @ui-action-discreet-type; + + &:hover { + color: @ui-action-discreet-type-hover; + background-color: @ui-action-discreet-hover; + } + } + + .editor-icon-wrapper { + width: 60px; + height: 60px; + text-align: center; + line-height: 60px; + flex: 0 0 60px; + padding-left: 10px; + + .icon { + font-size: 32px; + } + } + + .editor-details { + flex: 1 1 auto; + margin-top: 10px; + margin-bottom: 10px; + + .editor-name { + display: block; + font-weight: bold; } - .editor-details { - float: left; - margin-top: 10px; - - .editor-name { - display: block; - font-weight: bold; - } - - .editor-editor { - display: block; - font-size: 12px; - } + .editor-editor { + display: block; + font-size: 12px; } + } - .editor-settings-icon { - font-size: 18px; - margin-top: 8px; + .editor-remove-icon { + flex: 0 0 48px; + width: 48px; + height: 48px; + font-size: 18px; + + min-height: 80px; + color: @ui-action-discreet-type; + &:hover { + color: @ui-action-discreet-type-hover; + background-color: @ui-action-discreet-hover; } } @@ -544,6 +584,11 @@ input.umb-group-builder__group-sort-value { margin-bottom: 20px; } + .editor-description { + margin-top: 20px; + padding: 0; + } + .editor-description, .editor-validation-pattern { min-width: 100%; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less index 96636a3096..6238900bf6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-lightbox.less @@ -32,14 +32,18 @@ .umb-lightbox__close { position: absolute; top: 20px; - right: 60px; + right: 20px; + height: 40px; + width: 40px; } .umb-lightbox__close i { font-size: 20px; - cursor: pointer; - height: 40px; - width: 40px; + height: inherit; + width: inherit; + position: absolute; + top: 0; + left: 0; } .umb-lightbox__images { @@ -75,12 +79,20 @@ right: 20px; top: 50%; transform: translate(0, -50%); + + .umb-lightbox__control-icon { + margin-right: -4px; + } } .umb-lightbox__control.-prev { left: 20px; top: 50%; transform: translate(0, -50%); + + .umb-lightbox__control-icon { + margin-left: -4px; + } } .umb-lightbox__control-icon { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less index 94cfa6f62c..44955e8f8e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-list.less @@ -1,15 +1,18 @@ .umb-list--condensed { .umb-list-item { - padding-top: 5px; - padding-bottom: 5px; + padding-top: 7px; + padding-bottom: 7px; } } .umb-list-item { - border-bottom: 1px solid @gray-9; + border-bottom: 1px solid @gray-11; padding-top: 15px; padding-bottom: 15px; display: flex; + &:last-of-type { + border-bottom: none; + } } a.umb-list-item:hover, diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less new file mode 100644 index 0000000000..9966fc97e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/content-templates.less @@ -0,0 +1,22 @@ +.content-templates-dashboard{ + p{ + line-height: 1.6em; + margin-bottom: 30px; + + &:last-child{ + margin-bottom: 0; + } + } + + ul{ + margin-bottom: 15px; + } + + li{ + margin-bottom: 5px; + + &:last-child{ + margin-bottom: 0; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less b/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less index 9b4bac723b..e5b84fc6ca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms/umb-validation-label.less @@ -1,13 +1,17 @@ .umb-validation-label { position: absolute; - top: 27px; - min-width: 100px; - max-width: 200px; - padding: 1px 5px; + z-index: 1; + top: 28px; + min-width: 80px; + max-width: 260px; + padding: 2px 6px; background: @red; color: @white; - font-size: 11px; + font-size: 12px; line-height: 1.5em; + border-radius: @baseBorderRadius; + pointer-events: none; + user-select: none; } .umb-validation-label:after { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index e36acdc273..818b1d84d1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -123,6 +123,7 @@ position: relative; text-align: right; user-select: none; + margin-left: auto; a { opacity: .5; @@ -134,8 +135,8 @@ .password-text { background-repeat: no-repeat; background-size: 18px; - background-position: left center; - padding-left: 26px; + background-position: 0px 1px; + padding-left: 24px; &.show { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E"); diff --git a/src/Umbraco.Web.UI.Client/src/less/properties.less b/src/Umbraco.Web.UI.Client/src/less/properties.less index 8523fe9300..9e951feb1a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/properties.less +++ b/src/Umbraco.Web.UI.Client/src/less/properties.less @@ -49,7 +49,7 @@ } .date-wrapper-mini--checkbox{ - margin: 0 0 0 26px; + margin: 0 0 0 28px; } .date-wrapper-mini__date { @@ -62,6 +62,10 @@ &:first-of-type { margin-left: 0; } + .flatpickr-input > button:first-of-type { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } } .date-wrapper-mini__date .flatpickr-input > a { 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 764b73c593..b5870b8dce 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -863,11 +863,11 @@ .bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;} .umb-datepicker .input-append .add-on{cursor: pointer;} .umb-datepicker .input-append .on-top { + border: 0 none; position: absolute; margin-left: -31px; margin-top: 1px; display: inline-block; - height: 22px; padding: 5px 6px 3px 6px; font-size: @baseFontSize; font-weight: normal; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index accd09fdd9..d94e4e1fb1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -83,6 +83,8 @@ @sand-5: #F3ECE8;// added 2019 @sand-6: #F6F1EF;// added 2019 @sand-7: #F9F7F5;// added 2019 +@sand-8: #fbfaf9;// added 2019 +@sand-9: #fdfcfc;// added 2019 // Additional Icon Colours diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 81eadf150f..297d93f4bc 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -56,12 +56,14 @@ function MainController($scope, $location, appState, treeService, notificationsS appState.setSearchState("show", false); }; - $scope.showLoginScreen = function(isTimedOut) { + $scope.showLoginScreen = function (isTimedOut) { + $scope.login.pageTitle = $scope.$root.locationTitle; $scope.login.isTimedOut = isTimedOut; $scope.login.show = true; }; - $scope.hideLoginScreen = function() { + $scope.hideLoginScreen = function () { + $scope.$root.locationTitle = $scope.login.pageTitle; $scope.login.show = false; }; diff --git a/src/Umbraco.Web.UI.Client/src/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/navigation.controller.js index ee8d13e320..a383c2d44a 100644 --- a/src/Umbraco.Web.UI.Client/src/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/navigation.controller.js @@ -495,7 +495,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //execute them sequentially // set selected language to active - $scope.languages.forEach(language => { + Utilities.forEach($scope.languages, language => { language.active = false; }); diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js index 71d904427b..9121ba0e25 100644 --- a/src/Umbraco.Web.UI.Client/src/utilities.js +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -94,6 +94,26 @@ return JSON.stringify(obj, toJsonReplacer, pretty); } + /** + * Equivalent to angular.fromJson + */ + const fromJson = (val) => { + if (!isString(val)) { + return val; + } + return JSON.parse(val); + } + + /** + * Not equivalent to angular.forEach. But like the angularJS method this does not fail on null or undefined. + */ + const forEach = (obj, iterator) => { + if (obj) { + return obj.forEach(iterator); + } + return obj; + } + let _utilities = { noop: noop, copy: copy, @@ -106,10 +126,12 @@ isString: isString, isNumber: isNumber, isObject: isObject, - toJson: toJson + fromJson: fromJson, + toJson: toJson, + forEach: forEach }; if (typeof (window.Utilities) === 'undefined') { window.Utilities = _utilities; } -})(window); \ No newline at end of file +})(window); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js new file mode 100644 index 0000000000..9ee80488af --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.controller.js @@ -0,0 +1,106 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.DataTypeConfigurationPickerController + * @function + * + * @description + * The controller for the content type editor data type configuration picker dialog + */ + +(function() { + "use strict"; + + function DataTypeConfigurationPicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) { + + var vm = this; + + vm.configs = []; + + vm.loading = true; + + vm.newDataType = newDataType; + vm.pickDataType = pickDataType; + vm.close = close; + + function activate() { + setTitle(); + load(); + } + + function setTitle() { + if (!$scope.model.title) { + localizationService.localize("defaultdialogs_selectEditorConfiguration") + .then(function(data){ + $scope.model.title = data; + }); + } + } + + function load() { + + dataTypeResource.getGroupedDataTypes().then(function(configs) { + + var filteredConfigs = []; + + _.each(configs, function(configGroup) { + for(var i = 0; i + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js index 167e74c25d..d622ccea52 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.controller.js @@ -13,30 +13,23 @@ function DataTypePicker($scope, $filter, dataTypeResource, contentTypeResource, localizationService, editorService) { var vm = this; - + + vm.showDataTypes = true; + vm.dataTypes = []; + vm.loading = true; + vm.loadingConfigs = false; vm.searchTerm = ""; - vm.showTabs = false; - vm.tabsLoaded = 0; - vm.typesAndEditors = []; - vm.userConfigured = []; - vm.loading = false; - vm.tabs = []; - vm.labels = {}; - - vm.onTabChange = onTabChange; - vm.filterItems = filterItems; - vm.showDetailsOverlay = showDetailsOverlay; - vm.hideDetailsOverlay = hideDetailsOverlay; - vm.pickEditor = pickEditor; + vm.searchResult = null; + + vm.viewOptionsForEditor = viewOptionsForEditor; vm.pickDataType = pickDataType; + vm.pickEditor = pickEditor; vm.close = close; + vm.searchTermChanged = searchTermChanged; function activate() { setTitle(); - loadTabs(); - getGroupedDataTypes(); - getGroupedPropertyEditors(); - + loadTypes(); } function setTitle() { @@ -44,128 +37,104 @@ localizationService.localize("defaultdialogs_selectEditor") .then(function(data){ $scope.model.title = data; - }); + } + ); } } - - function loadTabs() { - - var labels = ["contentTypeEditor_availableEditors", "contentTypeEditor_reuse"]; - - localizationService.localizeMany(labels) - .then(function(data){ - vm.labels.availableDataTypes = data[0]; - vm.labels.reuse = data[1]; - - vm.tabs = [{ - active: true, - id: 1, - label: vm.labels.availableDataTypes, - alias: "Default", - typesAndEditors: [] - }, { - active: false, - id: 2, - label: vm.labels.reuse, - alias: "Reuse", - userConfigured: [] - }]; - - }); - } - - function getGroupedPropertyEditors() { - - vm.loading = true; - - dataTypeResource.getGroupedPropertyEditors().then(function(data) { - vm.tabs[0].typesAndEditors = data; - vm.typesAndEditors = data; - vm.tabsLoaded = vm.tabsLoaded + 1; - checkIfTabContentIsLoaded(); - }); - - } - - function getGroupedDataTypes() { - - vm.loading = true; - - dataTypeResource.getGroupedDataTypes().then(function(data) { - vm.tabs[1].userConfigured = data; - vm.userConfigured = data; - vm.tabsLoaded = vm.tabsLoaded + 1; - checkIfTabContentIsLoaded(); - }); - - } - - function checkIfTabContentIsLoaded() { - if (vm.tabsLoaded === 2) { + + function loadTypes() { + + dataTypeResource.getGroupedPropertyEditors().then(function(dataTypes) { + vm.dataTypes = dataTypes; vm.loading = false; - vm.showTabs = true; - } - } - - function onTabChange(selectedTab) { - vm.tabs.forEach(function(tab) { - tab.active = false; }); - selectedTab.active = true; + + } + + function loadConfigurations() { + + vm.loading = true; + vm.loadingConfigs = true; + + dataTypeResource.getGroupedDataTypes().then(function(configs) { + vm.configs = configs; + vm.loading = false; + performeSearch(); + }); + } - function filterItems() { - // clear item details - $scope.model.itemDetails = null; - - if (vm.searchTerm) { - vm.showTabs = false; - - var regex = new RegExp(vm.searchTerm, "i"); - - var userConfigured = filterCollection(vm.userConfigured, regex), - typesAndEditors = filterCollection(vm.typesAndEditors, regex); - - var totalResults = _.reduce(_.pluck(_.union(userConfigured, typesAndEditors), 'count'), (m, n) => m + n, 0); - - vm.filterResult = { - userConfigured: userConfigured, - typesAndEditors: typesAndEditors, - totalResults: totalResults - }; - + + function searchTermChanged() { + + vm.showDataTypes = (vm.searchTerm === ""); + + if(vm.loadingConfigs !== true) { + loadConfigurations() } else { - vm.filterResult = null; - vm.showTabs = true; + performeSearch(); } + } - + + function performeSearch() { + + if (vm.searchTerm) { + if (vm.configs) { + + var regex = new RegExp(vm.searchTerm, "i"); + vm.searchResult = { + configs: filterCollection(vm.configs, regex), + dataTypes: filterCollection(vm.dataTypes, regex) + }; + } + } else { + vm.searchResult = null; + } + + } + function filterCollection(collection, regex) { return _.map(_.keys(collection), function (key) { - - var filteredDataTypes = $filter('filter')(collection[key], function (dataType) { - return regex.test(dataType.name) || regex.test(dataType.alias); - }); - return { group: key, - count: filteredDataTypes.length, - dataTypes: filteredDataTypes + entries: $filter('filter')(collection[key], function (dataType) { + return regex.test(dataType.name) || regex.test(dataType.alias); + }) } }); } - function showDetailsOverlay(property) { + + function viewOptionsForEditor(editor) { + + var dataTypeConfigurationPicker = { + editor: editor, + property: $scope.model.property, + contentTypeName: $scope.model.contentTypeName, + view: "views/common/infiniteeditors/datatypeconfigurationpicker/datatypeconfigurationpicker.html", + size: "small", + submit: function(dataType, propertyType, isNew) { + submit(dataType, propertyType, isNew); + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; - var propertyDetails = {}; - propertyDetails.icon = property.icon; - propertyDetails.title = property.name; + editorService.open(dataTypeConfigurationPicker); - $scope.model.itemDetails = propertyDetails; } - function hideDetailsOverlay() { - $scope.model.itemDetails = null; + function pickDataType(selectedDataType) { + selectedDataType.loading = true; + dataTypeResource.getById(selectedDataType.id).then(function(dataType) { + contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) { + selectedDataType.loading = false; + submit(dataType, propertyType, false); + }); + }); } function pickEditor(propertyEditor) { @@ -188,16 +157,7 @@ }; editorService.open(dataTypeSettings); - } - function pickDataType(selectedDataType) { - selectedDataType.loading = true; - dataTypeResource.getById(selectedDataType.id).then(function(dataType) { - contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function(propertyType) { - selectedDataType.loading = false; - submit(dataType, propertyType, false); - }); - }); } function submit(dataType, propertyType, isNew) { @@ -213,9 +173,9 @@ $scope.model.submit($scope.model); } - + function close() { - if ($scope.model.close) { + if($scope.model.close) { $scope.model.close(); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html index 534fdc5648..768f8a8c24 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypepicker/datatypepicker.html @@ -15,127 +15,78 @@ -
- + - -
- - - -
-
-
{{key}}
-
    -
  • - - - - {{ systemDataType.name }} - - -
  • -
-
-
-
-
-
{{key}}
-
    -
  • -
    -
    -
    - - - - {{ dataType.name }} - - -
  • -
-
-
-
+
+
+
{{key | umbCmsTitleCase}}
+ +
- -
-
-
-
-
-
{{result.group}}
-
-
-
-
-
-
{{result.group}}
-
- - - -
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html index 8acaa544c1..428905020b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/datatypesettings/datatypesettings.html @@ -21,7 +21,7 @@ -
+
using this editor will get updated with the new settings.
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.controller.js index 83c49d604f..ba577045aa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/macropicker/macropicker.controller.js @@ -70,8 +70,8 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi //detect if it is a json string if (val.detectIsJson()) { try { - //Parse it to json - prop.value = JSON.parse(val); + //Parse it from json + prop.value = Utilities.fromJson(val); } catch (e) { // not json diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index 9640f2eba2..a6d1383640 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -30,6 +30,7 @@ vm.close = close; vm.toggleAllowCultureVariants = toggleAllowCultureVariants; + vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants; vm.toggleValidation = toggleValidation; vm.toggleShowOnMemberProfile = toggleShowOnMemberProfile; vm.toggleMemberCanEdit = toggleMemberCanEdit; @@ -114,7 +115,7 @@ property: $scope.model.property, contentTypeName: $scope.model.contentTypeName, view: "views/common/infiniteeditors/datatypepicker/datatypepicker.html", - size: "small", + size: "medium", submit: function(model) { $scope.model.updateSameDataTypes = model.updateSameDataTypes; @@ -248,6 +249,10 @@ $scope.model.property.allowCultureVariant = toggleValue($scope.model.property.allowCultureVariant); } + function toggleAllowSegmentVariants() { + $scope.model.property.allowSegmentVariant = toggleValue($scope.model.property.allowSegmentVariant); + } + function toggleValidation() { $scope.model.property.validation.mandatory = toggleValue($scope.model.property.validation.mandatory); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 77ee276e3e..1b28e84607 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -18,8 +18,8 @@
-
-
+
+
- +
- + - - - -
- - - - - -
- {{ model.property.dataTypeName }} - {{ model.property.editor }} + +
+ Required editor
- - - - - + + +
+ + + +
- +
- +
@@ -132,19 +139,29 @@ ng-keypress="vm.submitOnEnter($event)" />
-
- -
- - - +
+ +
+ +
- -
+
+ +
+ + + + +
+ +
@@ -177,7 +194,7 @@ checked="model.property.isSensitiveData" on-click="vm.toggleIsSensitiveData()"> - +
@@ -207,7 +224,7 @@ label-key="general_submit" action="vm.submit(model)"> - + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html index 9b230410b0..e292a94606 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -9,9 +9,9 @@ hide-icon="true" hide-description="true"> - + - + @@ -21,21 +21,21 @@
-
- +
- +

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

- +
- +
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html index e1eb5e454a..0772dea148 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html @@ -10,6 +10,7 @@ page="page" content="content" culture="culture" + segment="segment" on-select-app="appChanged(app)" on-select-app-anchor="appAnchorChanged(app, anchor)" on-back="onBack()" @@ -61,9 +62,9 @@ shortcut="ctrl+s" add-ellipsis="{{page.saveButtonEllipsis}}"> - - + show-inherit="propertyEditorDisabled(property)" + inherits-from="defaultVariant.displayName"> -
+
+ preview="propertyEditorDisabled(property)">
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content-editors.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content-editors.html index 0e9e7d8eab..247453c604 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content-editors.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-variant-content-editors.html @@ -1,7 +1,6 @@ 
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html index fe90fef07a..ceae7c3bb9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-menu.html @@ -15,7 +15,14 @@ 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 ba5adf199a..5f83d9ae53 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 @@ -1,7 +1,7 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html index dca3849491..a3530f77b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-node-preview.html @@ -3,7 +3,7 @@
-
{{ name }}
+
{{ name }}
{{ description }}
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js index 2a3f67a7e3..66747a8697 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.controller.js @@ -7,35 +7,62 @@ //if we make the viewModel the variant itself, we end up with a circular reference in the models which isn't ideal // (i.e. variant.apps[contentApp].viewModel = variant) //so instead since we already have access to the content, we can just get the variant directly by the index. + + var unbindLanguageWatcher = function() {}; + var unbindSegmentWatcher = function() {}; + var timeout = null; var vm = this; vm.loading = true; function onInit() { - //get the variant by index (see notes above) - vm.content = $scope.content.variants[$scope.model.viewModel]; + serverValidationManager.notify(); vm.loading = false; + timeout = null;// ensure timeout is set to null, so we know that its not running anymore. //if this variant has a culture/language assigned, then we need to watch it since it will change //if the language drop down changes and we need to re-init - if (vm.content.language) { - $scope.$watch(function () { - return vm.content.language.culture; + if ($scope.variantContent) { + if ($scope.variantContent.language) { + unbindLanguageWatcher = $scope.$watch(function () { + return $scope.variantContent.language.culture; + }, function (newVal, oldVal) { + if (newVal !== oldVal) { + requestUpdate(); + } + }); + } + + unbindSegmentWatcher = $scope.$watch(function () { + return $scope.variantContent.segment; }, function (newVal, oldVal) { if (newVal !== oldVal) { - vm.loading = true; - - // TODO: Can we minimize the flicker? - $timeout(function () { - onInit(); - }, 100); + requestUpdate(); } }); } + + } + + function requestUpdate() { + if (timeout === null) { + vm.loading = true; + + // TODO: Can we minimize the flicker? + timeout = $timeout(function () { + onInit(); + }, 100); + } } onInit(); + + $scope.$on("$destroy", function() { + unbindLanguageWatcher(); + unbindSegmentWatcher(); + $timeout.cancel(timeout); + }); } angular.module("umbraco").controller("Umbraco.Editors.Content.Apps.ContentController", ContentAppContentController); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html index d391f2cd95..7023a4187a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/apps/content/content.html @@ -2,7 +2,8 @@ + content-node-model="content" + content="variantContent"> - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index 1a1254fcf1..fc7d38fd37 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -64,6 +64,9 @@ function contentCreateController($scope, language as what is selected in the tree */ .search("cculture", mainCulture) /* when we create a new node we must make sure that any previously + opened segments is reset */ + .search("csegment", null) + /* when we create a new node we must make sure that any previously used blueprint is reset */ .search("blueprintId", null); close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index ad79bf01d1..2c7c53e31f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -28,6 +28,7 @@ function ContentEditController($scope, $routeParams, contentResource) { $scope.isNew = infiniteMode ? $scope.model.create : $routeParams.create; //load the default culture selected in the main tree if any $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + $scope.segment = $routeParams.csegment ? $routeParams.csegment : null; //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change @@ -36,6 +37,7 @@ function ContentEditController($scope, $routeParams, contentResource) { //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.segment = next.params.csegment ? next.params.csegment : null; $scope.isNew = next.params.create === "true"; $scope.contentId = infiniteMode ? $scope.model.id : $routeParams.id; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/content/edit.html index ddd9dd35ec..94e8c25b89 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/edit.html @@ -8,6 +8,7 @@ tree-alias="content" is-new="isNew" culture="culture" + segment="segment" infinite-model="model">
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js index 8caa38ea17..decf05be5f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js @@ -5,193 +5,171 @@ var vm = this; vm.loading = true; - vm.hasPristineVariants = false; vm.isNew = true; vm.changeSelection = changeSelection; - vm.dirtyVariantFilter = dirtyVariantFilter; - vm.pristineVariantFilter = pristineVariantFilter; - /** Returns true if publishing is possible based on if there are un-published mandatory languages */ + /** Returns true if publish meets the requirements of mandatory languages */ function canPublish() { - var possible = false; + var hasSomethingToPublish = false; + for (var i = 0; i < vm.variants.length; i++) { var variant = vm.variants[i]; - var state = canVariantPublish(variant); - if (state === true) { - possible = true; - } - if (state === false) { + + // if varaint is mandatory and not already published: + if (variant.publish === false && notPublishedMandatoryFilter(variant)) { return false; } + if (variant.publish === true) { + hasSomethingToPublish = true; + } + } - return possible; - } - - /** Returns true if publishing is possible based on if the variant is a un-published mandatory language */ - function canVariantPublish(variant) { - - //if this variant will show up in the publish-able list - var publishable = dirtyVariantFilter(variant); - var published = !(variant.state === "NotCreated" || variant.state === "Draft"); - - // is this variant mandatory: - if (variant.language.isMandatory && !published && !variant.publish) { - //if a mandatory variant isn't published or set to be published - //then we cannot continue - - return false; - } - - // is this variant selected for publish: - if (variant.publish === true) { - return publishable; - } - - return null; + return hasSomethingToPublish; } function changeSelection(variant) { - + // update submit button state: $scope.model.disableSubmitButton = !canPublish(); - //need to set the Save state to true if publish is true + //need to set the Save state to same as publish. variant.save = variant.publish; - - variant.willPublish = canVariantPublish(variant); } - function dirtyVariantFilter(variant) { - //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's - // * the active one - // * it's editor is in a $dirty state - // * it has pending saves - // * it is unpublished - // * it is in NotCreated state - return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated"); - } - function hasAnyData(variant) { + function hasAnyDataFilter(variant) { if (variant.name == null || variant.name.length === 0) { return false; } - var result = variant.isDirty != null; - - if(result) return true; + if(variant.isDirty === true) { + return true; + } for (var t=0; t < variant.tabs.length; t++){ for (var p=0; p < variant.tabs[t].properties.length; p++){ - var property = variant.tabs[t].properties[p]; - - if(property.culture == null) continue; - - result = result || (property.value != null && property.value.length > 0); - - if(result) return true; + if (property.value != null && property.value.length > 0) { + return true; + } } } - return result; + return false; } - function pristineVariantFilter(variant) { - return !(dirtyVariantFilter(variant)); + function dirtyVariantFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * it's editor is in a $dirty state + // * it has pending saves + // * it is unpublished + return (variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); + } + + function publishableVariantFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * variant is active + // * it's editor is in a $dirty state + // * it has pending saves + // * it is unpublished + return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); + } + + function notPublishedMandatoryFilter(variant) { + return variant.state !== "Published" && isMandatoryFilter(variant); + } + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); + } + function notPublishableButMandatoryFilter(variant) { + //determine a variant is needed, but not already a choice. + // * publishable — aka. displayed as a publish option. + // * published — its already published and everything is then fine. + // * mandatory — this is needed, and thats why we highlight it. + return !publishableVariantFilter(variant) && variant.state !== "Published" && variant.isMandatory === true; } function onInit() { - - vm.variants = $scope.model.variants; - if (!$scope.model.title) { - localizationService.localize("content_readyToPublish").then(function (value) { - $scope.model.title = value; - }); - } + _.each(vm.variants, (variant) => { + + // reset to not be published + variant.publish = false; + variant.save = false; + + variant.isMandatory = isMandatoryFilter(variant); - vm.hasPristineVariants = false; - - _.each(vm.variants, - function (variant) { - if(variant.state === "NotCreated") { - vm.isNew = true; - } + // If we have a variant thats not in the state of NotCreated, then we know we have adata and its not a new content node. + if(variant.state !== "NotCreated") { + vm.isNew = false; } - ); - - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - - // reset to not be published - variant.publish = false; - variant.save = false; - - //check for pristine variants - if (!vm.hasPristineVariants) { - vm.hasPristineVariants = pristineVariantFilter(variant); - } - - // If the variant havent been created jet. - if(variant.state === "NotCreated") { - // If the variant is mandatory, then set the variant to be published. - if (variant.language.isMandatory === true) { - variant.publish = true; - variant.save = true; - } - } - - variant.canPublish = dirtyVariantFilter(variant); - - // if we have data on this variant. - if(variant.canPublish && hasAnyData(variant)) { - // and if some varaints havent been saved before, or they dont have a publishing date set, then we set it for publishing. - if(vm.isNew || variant.publishDate == null){ - variant.publish = true; - variant.save = true; - } - } - - variant.willPublish = canVariantPublish(variant); - } - ); - - if (vm.variants.length !== 0) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; - }); - - var active = _.find(vm.variants, function (v) { - return v.active; - }); - - if (active) { - //ensure that the current one is selected - active.publish = true; - active.save = true; - } - - $scope.model.disableSubmitButton = !canPublish(); - - } else { - //disable Publish button if we have nothing to publish, should not happen - $scope.model.disableSubmitButton = true; - } - - var labelKey = vm.isNew ? "content_languagesToPublishForFirstTime" : "content_languagesToPublish"; - - localizationService.localize(labelKey).then(function (value) { - vm.headline = value; - vm.loading = false; }); + _.each(vm.variants, (variant) => { + + // if this is a new node and we have data on this variant. + if(vm.isNew === true && hasAnyDataFilter(variant)) { + variant.save = true; + } + + }); + vm.availableVariants = vm.variants.filter(publishableVariantFilter); + vm.missingMandatoryVariants = vm.variants.filter(notPublishableButMandatoryFilter); + + // if any active varaiant that is available for publish, we set it to be published: + _.each(vm.availableVariants, (v) => { + if(v.active) { + v.save = v.publish = true; + } + }); + + if (vm.availableVariants.length !== 0) { + vm.availableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; + }); + } + + + $scope.model.disableSubmitButton = !canPublish(); + + if (vm.missingMandatoryVariants.length > 0) { + localizationService.localize("content_notReadyToPublish").then(function (value) { + $scope.model.title = value; + vm.loading = false; + }); + } else { + if (!$scope.model.title) { + localizationService.localize("content_readyToPublish").then(function (value) { + $scope.model.title = value; + vm.loading = false; + }); + } else { + vm.loading = false; + } + } } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index 80c8cfd485..4cf4444cfe 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -1,67 +1,83 @@ -
- -
-

{{vm.headline}}

-
+
-
+
+ +
+

+
+ ng-repeat="variant in vm.availableVariants track by variant.compositeId"> -
+
-
- - - - - - + server-validation-field="{{variant.htmlId}}" + > + + + * + + + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + + - - {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - + - -
-
+
+
+

+
-
-
-

+
+
+

-
-
-
- {{variant.language.name}} - * -
+
+
+ + + * + + + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + -
- -
- -
-
{{notification.message}}
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js index 0ca2fe65c9..095c4f3fe1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js @@ -5,12 +5,16 @@ var vm = this; + vm.includeUnpublished = false; + vm.changeSelection = changeSelection; + vm.toggleIncludeUnpublished = toggleIncludeUnpublished; + function onInit() { - vm.includeUnpublished = false; vm.variants = $scope.model.variants; + vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications). vm.labels = {}; if (!$scope.model.title) { @@ -19,17 +23,30 @@ }); } - _.each(vm.variants, - function (variant) { - variant.compositeId = (variant.language ? variant.language.culture : "inv") + "_" + (variant.segment ? variant.segment : ""); - variant.htmlId = "_content_variant_" + variant.compositeId; - }); + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + }); if (vm.variants.length > 1) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + vm.displayVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); var active = _.find(vm.variants, function (v) { @@ -48,14 +65,17 @@ // localize help text for invariant content vm.labels.help = { "key": "content_publishDescendantsHelp", - "tokens": [] + "tokens": [vm.variants[0].name] }; - // add the node name as a token so it will show up in the translated text - vm.labels.help.tokens.push(vm.variants[0].name); } } + function toggleIncludeUnpublished() { + console.log("toggleIncludeUnpublished") + vm.includeUnpublished = !vm.includeUnpublished; + } + /** Returns true if publishing is possible based on if there are un-published mandatory languages */ function canPublish() { var selected = []; @@ -64,7 +84,7 @@ var published = !(variant.state === "NotCreated" || variant.state === "Draft"); - if (variant.language.isMandatory && !published && !variant.publish) { + if (variant.segment == null && variant.language && variant.language.isMandatory && !published && !variant.publish) { //if a mandatory variant isn't published //and not flagged for saving //then we cannot continue @@ -86,6 +106,14 @@ variant.save = variant.publish; } + + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); + } + //when this dialog is closed, reset all 'publish' flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index 0bf434f9a4..92373d00ff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -1,67 +1,69 @@ -
+
-
+

- +
-
+

- -
- -
- +
-
+
-
+
- - -
- - - - - - - - - {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - - - + > + + + * -
-
+ + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js index aacea9fe0e..2d40005618 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.controller.js @@ -9,8 +9,6 @@ vm.isNew = true; vm.changeSelection = changeSelection; - vm.dirtyVariantFilter = dirtyVariantFilter; - vm.pristineVariantFilter = pristineVariantFilter; function changeSelection(variant) { var firstSelected = _.find(vm.variants, function (v) { @@ -19,16 +17,18 @@ $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } - function dirtyVariantFilter(variant) { + function saveableVariantFilter(variant) { //determine a variant is 'dirty' (meaning it will show up as save-able) if it's // * the active one // * it's editor is in a $dirty state - // * it is in NotCreated state return (variant.active || variant.isDirty); } - function pristineVariantFilter(variant) { - return !(dirtyVariantFilter(variant)); + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } function hasAnyData(variant) { @@ -57,6 +57,7 @@ function onInit() { vm.variants = $scope.model.variants; + vm.availableVariants = vm.variants.filter(saveableVariantFilter); if(!$scope.model.title) { localizationService.localize("content_readyToSave").then(function(value){ @@ -64,10 +65,15 @@ }); } - vm.hasPristineVariants = false; - _.each(vm.variants, function (variant) { + + //reset state: + variant.save = false; + variant.publish = false; + + variant.isMandatory = isMandatoryFilter(variant); + if(variant.state !== "NotCreated"){ vm.isNew = false; } @@ -75,34 +81,40 @@ _.each(vm.variants, function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - - //check for pristine variants - if (!vm.hasPristineVariants) { - vm.hasPristineVariants = pristineVariantFilter(variant); - } - if(vm.isNew && hasAnyData(variant)){ variant.save = true; } }); if (vm.variants.length !== 0) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + + _.find(vm.variants, function (v) { + if(v.active) { + //ensure that the current one is selected + v.save = true; + } }); - var active = _.find(vm.variants, function (v) { - return v.active; + vm.availableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); - if (active) { - //ensure that the current one is selected - active.save = true; - } - } else { //disable save button if we have nothing to save $scope.model.disableSubmitButton = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html index 552f6003b0..dd6e96df95 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html @@ -1,4 +1,4 @@ -
+
@@ -7,17 +7,16 @@

- - +

+ ng-repeat="variant in vm.availableVariants track by variant.compositeId"> -
+
+ > + + + * + + + + — {{variant.language.name}} + * + + + + - + + + + {{saveVariantSelectorForm.saveVariantSelector.errorMsg}} + + -
- - - - - + - - {{saveVariantSelectorForm.saveVariantSelector.errorMsg}} - - - -

- -
-
-

- - -

-
- -
-
-
- {{ variant.language.name }} - * -
- -
- -
- -
-
{{notification.message}}
-
-
-
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js index 5aa7eff1ef..e7f31ac707 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.controller.js @@ -12,7 +12,6 @@ vm.clearPublishDate = clearPublishDate; vm.clearUnpublishDate = clearUnpublishDate; vm.dirtyVariantFilter = dirtyVariantFilter; - vm.pristineVariantFilter = pristineVariantFilter; vm.changeSelection = changeSelection; vm.firstSelectedDates = {}; @@ -24,7 +23,7 @@ function onInit() { vm.variants = $scope.model.variants; - vm.hasPristineVariants = false; + vm.displayVariants = vm.variants.slice(0);// shallow copy, we dont want to share the array-object(because we will be performing a sort method) but each entry should be shared (because we need validation and notifications). for (let i = 0; i < vm.variants.length; i++) { origDates.push({ @@ -38,36 +37,42 @@ $scope.model.title = value; }); } + + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + + }); // Check for variants: if a node is invariant it will still have the default language in variants // so we have to check for length > 1 if (vm.variants.length > 1) { - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - - //check for pristine variants - if (!vm.hasPristineVariants) { - vm.hasPristineVariants = pristineVariantFilter(variant); + vm.displayVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; } - }); - - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); - var active = _.find(vm.variants, function (v) { - return v.active; + _.each(vm.variants, function (v) { + if (v.active) { + v.save = true; + } }); - - if (active) { - //ensure that the current one is selected - active.save = true; - } - + $scope.model.disableSubmitButton = !canSchedule(); } @@ -127,6 +132,7 @@ * @param {any} type publish or unpublish */ function datePickerChange(variant, dateStr, type) { + console.log("datePickerChange", variant, dateStr, type) if (type === 'publish') { setPublishDate(variant, dateStr); } else if (type === 'unpublish') { @@ -241,6 +247,7 @@ * @param {any} variant */ function clearPublishDate(variant) { + console.log("clearPublishDate", variant, variant.releaseDate) if(variant && variant.releaseDate) { variant.releaseDate = null; // we don't have a publish date anymore so we can clear the min date for unpublish @@ -256,6 +263,7 @@ * @param {any} variant */ function clearUnpublishDate(variant) { + console.log("clearUnpublishDate", variant) if(variant && variant.expireDate) { variant.expireDate = null; // we don't have a unpublish date anymore so we can clear the max date for publish @@ -297,8 +305,11 @@ return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges" || variant.state === "NotCreated"); } - function pristineVariantFilter(variant) { - return !(dirtyVariantFilter(variant)); + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } /** Returns true if publishing is possible based on if there are un-published mandatory languages */ @@ -321,7 +332,7 @@ return true; } - var isMandatory = variant.language && variant.language.isMandatory; + var isMandatory = variant.segment == null && variant.language && variant.language.isMandatory; //if this variant will show up in the publish-able list var publishable = dirtyVariantFilter(variant); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html index 4f4e3c2874..8f515a10e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html @@ -1,4 +1,4 @@ -
+
@@ -84,28 +84,39 @@
-
+
-
+
+ server-validation-field="{{variant.htmlId}}" + > + + + * + + + + — {{variant.language.name}} + * + + + + - + +
- - - - - +
-
+
Publish:  {{variant.releaseDateFormatted}}
@@ -117,7 +128,7 @@ on-open="vm.datePickerShow(variant, 'publish')" on-close="vm.datePickerClose(variant, 'publish')">
- @@ -126,9 +137,9 @@
- +
@@ -143,7 +154,7 @@ on-open="vm.datePickerShow(variant, 'unpublish')" on-close="vm.datePickerClose(variant, 'unpublish')">
- @@ -152,7 +163,7 @@
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js index c85e8d7013..f425dce4a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.controller.js @@ -6,8 +6,6 @@ var vm = this; vm.loading = true; - vm.modifiedVariantFilter = modifiedVariantFilter; - vm.unmodifiedVariantFilter = unmodifiedVariantFilter; vm.changeSelection = changeSelection; function onInit() { @@ -21,28 +19,40 @@ }); } + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + }); + + vm.availableVariants = vm.variants.filter(publishableVariantFilter); + + if (vm.availableVariants.length !== 0) { - if (vm.variants.length !== 0) { - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - }); - - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + vm.availableVariants = vm.availableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); - var active = _.find(vm.variants, function (v) { - return v.active; + _.each(vm.availableVariants, function (v) { + if(v.active) { + v.save = true; + } }); - if (active) { - //ensure that the current one is selected - active.save = true; - } - } else { //disable save button if we have nothing to save $scope.model.disableSubmitButton = true; @@ -59,20 +69,20 @@ $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected } - function modifiedVariantFilter(variant) { - //determine a variant is 'modified' (meaning it will show up as able to send for approval) - // * it's editor is in a $dirty state - // * it is in Draft state - // * it is published with pending changes - return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } - function unmodifiedVariantFilter(variant) { - //determine a variant is 'unmodified' (meaning it will NOT show up as able to send for approval) + function publishableVariantFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * variant is active // * it's editor is in a $dirty state - // * it has been published - // * it is not created for that specific language - return (variant.state === "Published" && !variant.isDirty && !variant.active || variant.state === "NotCreated" && !variant.isDirty && !variant.active); + // * it has pending saves + // * it is unpublished + return (variant.active || variant.isDirty || variant.state === "Draft" || variant.state === "PublishedPendingChanges"); } //when this dialog is closed, reset all 'save' flags diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index ae8cd87484..91fa1799fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -1,4 +1,4 @@ -
+

@@ -10,31 +10,36 @@
-
+
-
+
- - -
- + > + + + * + + + + — {{variant.language.name}} + * + + - - + - + - - - {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + - -
+
@@ -42,20 +47,4 @@
-
-
-

-
- -
-
-
- {{ variant.language.name }} - * -
- -
-
-
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js index 3011bf49ee..3db76ee49f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.controller.js @@ -7,12 +7,11 @@ var autoSelectedVariants = []; vm.changeSelection = changeSelection; - vm.publishedVariantFilter = publishedVariantFilter; - vm.unpublishedVariantFilter = unpublishedVariantFilter; function onInit() { vm.variants = $scope.model.variants; + vm.unpublishableVariants = vm.variants.filter(publishedVariantFilter) // set dialog title if (!$scope.model.title) { @@ -21,24 +20,38 @@ }); } - _.each(vm.variants, - function (variant) { - variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant); - variant.htmlId = "_content_variant_" + variant.compositeId; - }); + _.each(vm.variants, function (variant) { + variant.isMandatory = isMandatoryFilter(variant); + }); // node has variants if (vm.variants.length !== 1) { - //now sort it so that the current one is at the top - vm.variants = _.sortBy(vm.variants, function (v) { - return v.active ? 0 : 1; + + vm.unpublishableVariants.sort(function (a, b) { + if (a.language && b.language) { + if (a.language.name > b.language.name) { + return -1; + } + if (a.language.name < b.language.name) { + return 1; + } + } + if (a.segment && b.segment) { + if (a.segment > b.segment) { + return -1; + } + if (a.segment < b.segment) { + return 1; + } + } + return 0; }); var active = _.find(vm.variants, function (v) { return v.active; }); - if (active) { + if (active && publishedVariantFilter(active)) { //ensure that the current one is selected active.save = true; } @@ -51,21 +64,15 @@ function changeSelection(selectedVariant) { - // disable submit button if nothing is selected - var firstSelected = _.find(vm.variants, function (v) { - return v.save; - }); - $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected - - // if a mandatory variant is selected we want to select all other variants + // if a mandatory variant is selected we want to select all other variants, we cant have anything published if a mandatory variants gets unpublished. // and disable selection for the others - if(selectedVariant.save && selectedVariant.language.isMandatory) { + if(selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) { - angular.forEach(vm.variants, function(variant){ - if(!variant.save && publishedVariantFilter(variant)) { + vm.variants.forEach(function(variant) { + if(!variant.save) { // keep track of the variants we automaically select // so we can remove the selection again - autoSelectedVariants.push(variant.language.culture); + autoSelectedVariants.push(variant); variant.save = true; } variant.disabled = true; @@ -79,12 +86,12 @@ // if a mandatory variant is deselected we want to deselet all the variants // that was automatically selected so it goes back to the state before the mandatory language was selected. // We also want to enable all checkboxes again - if(!selectedVariant.save && selectedVariant.language.isMandatory) { + if(!selectedVariant.save && selectedVariant.segment == null && selectedVariant.language && selectedVariant.language.isMandatory) { - angular.forEach(vm.variants, function(variant){ + vm.variants.forEach( function(variant){ // check if variant was auto selected, then deselect - if(_.contains(autoSelectedVariants, variant.language.culture)) { + if(_.contains(autoSelectedVariants, variant)) { variant.save = false; }; @@ -93,6 +100,19 @@ autoSelectedVariants = []; } + // disable submit button if nothing is selected + var firstSelected = _.find(vm.variants, function (v) { + return v.save; + }); + $scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected + + } + + function isMandatoryFilter(variant) { + //determine a variant is 'dirty' (meaning it will show up as publish-able) if it's + // * has a mandatory language + // * without having a segment, segments cant be mandatory at current state of code. + return (variant.language && variant.language.isMandatory === true && variant.segment == null); } function publishedVariantFilter(variant) { @@ -102,13 +122,6 @@ return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); } - function unpublishedVariantFilter(variant) { - //determine a variant is 'modified' (meaning it will NOT show up as able to unpublish) - // * it's editor is in a $dirty state - // * it is published with pending changes - return (variant.state !== "Published" && variant.state !== "PublishedPendingChanges"); - } - //when this dialog is closed, remove all unpublish and disabled flags $scope.$on('$destroy', function () { for (var i = 0; i < vm.variants.length; i++) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html index e384823cc3..94dd9fdac5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/unpublish.html @@ -1,4 +1,4 @@ -
+
@@ -10,12 +10,12 @@

- +
- -
+ +
-
+
- -
- - - - + > + + + * -
+ + + — {{variant.language.name}} + * + + + + - + + + + {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} + +
+ + +
- +

- -
-
-

-
- -
-
-
- {{ variant.language.name }} - * -
- -
- -
-
-
-
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js index 982af76d69..15112650c6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.controller.js @@ -44,6 +44,7 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) { //load the default culture selected in the main tree if any $scope.culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + $scope.segment = $routeParams.csegment ? $routeParams.csegment : null; //Bind to $routeUpdate which will execute anytime a location changes but the route is not triggered. //This is so we can listen to changes on the cculture parameter since that will not cause a route change @@ -52,6 +53,7 @@ function ContentBlueprintEditController($scope, $routeParams, contentResource) { //will not cause a route change and so we can update the isNew and contentId flags accordingly. $scope.$on('$routeUpdate', function (event, next) { $scope.culture = next.params.cculture ? next.params.cculture : $routeParams.mculture; + $scope.segment = next.params.csegment ? next.params.csegment : null; $scope.isNew = $routeParams.id === "-1"; $scope.contentId = $routeParams.id; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.html b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.html index 191dd4db5c..751bbe5970 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/edit.html @@ -5,6 +5,7 @@ get-scaffold-method="getScaffoldMethod" is-new="isNew" tree-alias="contentblueprints" - culture="culture"> + culture="culture" + segment="segment">
diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html index ce423225f6..66695ace91 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/intro.html @@ -12,21 +12,32 @@ - -

What are Content Templates?

-

Content Templates are pre-defined content that can be selected when creating a new content node.

+ +

+ What are Content Templates? +

+

+ Content Templates are pre-defined content that can be selected when creating a new content node. +

-

How do I create a Content Template?

-

There are two ways to create a Content Template:

-
    -
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • -
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • -
-

Once given a name, editors can start using the Content Template as a foundation for their new page.

+

+ How do I create a Content Template? +

+ +

There are two ways to create a Content Template:

+
    +
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • +
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • +
+

Once given a name, editors can start using the Content Template as a foundation for their new page.

+
-

How do I manage Content Templates

-

You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the - Content Template is based on and click it to edit or delete it.

+

+ How do I manage Content Templates? +

+

+ You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the Content Template is based on and click it to edit or delete it. +

diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js index f5ceb877c8..991a8132cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/healthcheck.controller.js @@ -33,7 +33,7 @@ var totalInfo = 0; // count total number of statusses - group.checks.forEach(check => { + Utilities.forEach(group.checks, check => { if (check.status) { check.status.forEach(status => { @@ -95,22 +95,24 @@ group.checkCounter = 0; group.loading = true; - checks.forEach(check => { - check.loading = true; + if (checks) { + checks.forEach(check => { + check.loading = true; - healthCheckResource.getStatus(check.id) - .then(function (response) { - check.status = response; - group.checkCounter = group.checkCounter + 1; - check.loading = false; + healthCheckResource.getStatus(check.id) + .then(function (response) { + check.status = response; + group.checkCounter = group.checkCounter + 1; + check.loading = false; - // when all checks are done, set global group result - if (group.checkCounter === checks.length) { - setGroupGlobalResultType(group); - group.loading = false; - } - }); - }); + // when all checks are done, set global group result + if (group.checkCounter === checks.length) { + setGroupGlobalResultType(group); + group.loading = false; + } + }); + }); + } } function openGroup(group) { 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 21eb77b1e9..42dba5f580 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 @@ -48,7 +48,7 @@ "shortcuts_navigateSections", "shortcuts_addGroup", "shortcuts_addProperty", - "shortcuts_addEditor", + "defaultdialogs_selectEditor", "shortcuts_editDataType", "shortcuts_toggleListView", "shortcuts_toggleAllowAsRoot", diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js index dd591090f9..da8dee8e03 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.controller.js @@ -20,12 +20,14 @@ vm.selectedChildren = []; vm.overlayTitle = ""; + vm.showAllowSegmentationOption = Umbraco.Sys.ServerVariables.umbracoSettings.showAllowSegmentationForDocumentTypes || false; vm.addChild = addChild; vm.removeChild = removeChild; vm.sortChildren = sortChildren; vm.toggleAllowAsRoot = toggleAllowAsRoot; vm.toggleAllowCultureVariants = toggleAllowCultureVariants; + vm.toggleAllowSegmentVariants = toggleAllowSegmentVariants; vm.canToggleIsElement = false; vm.toggleIsElement = toggleIsElement; @@ -110,6 +112,10 @@ $scope.model.allowCultureVariant = $scope.model.allowCultureVariant ? false : true; } + function toggleAllowSegmentVariants() { + $scope.model.allowSegmentVariant = $scope.model.allowSegmentVariant ? false : true; + } + function toggleIsElement() { $scope.model.isElement = $scope.model.isElement ? false : true; } diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html index e29956a48e..b44ce0ff7e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html @@ -43,8 +43,8 @@
-
- +
+
@@ -57,6 +57,23 @@
+ +
+ +
+
+ +
+ +
+ + +
+ +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html index 7ed6735292..f6ae77ebc4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html @@ -16,7 +16,7 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/macros/infiniteeditors/parameter.html b/src/Umbraco.Web.UI.Client/src/views/macros/infiniteeditors/parameter.html index c1f83cd0b7..5a18ddf2cb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/macros/infiniteeditors/parameter.html +++ b/src/Umbraco.Web.UI.Client/src/views/macros/infiniteeditors/parameter.html @@ -47,13 +47,18 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index e697dc56a5..b35663c3df 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -20,10 +20,11 @@ ng-required="model.validation.mandatory" val-server="value" class="datepickerinput"> - - - - + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js index a8979c949b..afbb4feb20 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js @@ -15,7 +15,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo //ensure this is a bool, old data could store zeros/ones or string versions $scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple); - + + //ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected + $scope.$on("formSubmitting", function () { + if ($scope.model.value.length === 0 || $scope.model.value[0] === null) { + $scope.model.value = null; + } + }); + function convertArrayToDictionaryArray(model){ //now we need to format the items in the dictionary because we always want to have an array var newItems = []; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 15f8dbf11c..71e334aee2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -34,6 +34,7 @@ fileManager.setFiles({ propertyAlias: $scope.model.alias, culture: $scope.model.culture, + segment: $scope.model.segment, files: [] }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index 2bc609714a..522278e99e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -1,5 +1,6 @@ 
Add - +
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 68c50c7ef7..066cbd6c17 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 @@ -1,6 +1,58 @@ (function () { 'use strict'; + /** + * When performing a copy, we do copy the ElementType Data Model, but each inner Nested Content property is still stored as the Nested Content Model, aka. each property is just storing its value. To handle this we need to ensure we handle both scenarios. + */ + + + angular.module('umbraco').run(['clipboardService', function (clipboardService) { + + function clearNestedContentPropertiesForStorage(prop, propClearingMethod) { + + // if prop.editor is "Umbraco.NestedContent" + if ((typeof prop === 'object' && prop.editor === "Umbraco.NestedContent")) { + + var value = prop.value; + for (var i = 0; i < value.length; i++) { + var obj = value[i]; + + // remove the key + delete obj.key; + + // Loop through all inner properties: + for (var k in obj) { + propClearingMethod(obj[k]); + } + } + } + } + + clipboardService.registrerClearPropertyResolver(clearNestedContentPropertiesForStorage) + + + function clearInnerNestedContentPropertiesForStorage(prop, propClearingMethod) { + + // if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property inside a NestedContent property. + if ((Array.isArray(prop) && prop.length > 0 && prop[0].ncContentTypeAlias !== undefined)) { + + for (var i = 0; i < prop.length; i++) { + var obj = prop[i]; + + // remove the key + delete obj.key; + + // Loop through all inner properties: + for (var k in obj) { + propClearingMethod(obj[k]); + } + } + } + } + + clipboardService.registrerClearPropertyResolver(clearInnerNestedContentPropertiesForStorage) + }]); + angular .module('umbraco') .component('nestedContentPropertyEditor', { @@ -13,7 +65,7 @@ } }); - function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) { + function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) { var vm = this; var model = $scope.$parent.$parent.model; @@ -76,7 +128,7 @@ } localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function (data) { - clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id); + clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id, clearNodeForCopy); }); } @@ -208,7 +260,8 @@ }); }); - vm.overlayMenu.title = vm.overlayMenu.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty; + vm.overlayMenu.title = labels.grid_addElement; + vm.overlayMenu.hideHeader = vm.overlayMenu.pasteItems.length > 0; vm.overlayMenu.clickClearPaste = function ($event) { $event.stopPropagation(); @@ -216,6 +269,7 @@ clipboardService.clearEntriesOfType("elementType", contentTypeAliases); clipboardService.clearEntriesOfType("elementTypeArray", contentTypeAliases); vm.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. + vm.overlayMenu.hideHeader = false; }; if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) { @@ -383,6 +437,11 @@ }); } + function clearNodeForCopy(clonedData) { + delete clonedData.key; + delete clonedData.$$hashKey; + } + vm.showCopy = clipboardService.isSupported(); vm.showPaste = false; @@ -390,7 +449,7 @@ syncCurrentNode(); - clipboardService.copy("elementType", node.contentTypeAlias, node); + clipboardService.copy("elementType", node.contentTypeAlias, node, null, null, null, clearNodeForCopy); $event.stopPropagation(); } 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 d255c4a5d6..87f6ffeac9 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 @@ -1,6 +1,6 @@
- + {{mandatoryMessage}} 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 35e7aa5176..4b51c44c18 100644 --- a/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/relationtypes/edit.html @@ -6,6 +6,7 @@ ul.nav > li.module { - background-color: #daf0c9; + background-color: #3544b1; } -.breadcrumb { - background-color: #f5fbf1; +.form-search > ul.nav > li.module a { + color: white; } -a { - color: #f36f21; +.form-search > ul.nav > li.section { + background-color: #ccc; } + +.breadcrumb { + background-color: #f7f7f7; +} + a:hover { text-decoration: none; color: rgba(0,0,0,.8); @@ -87,9 +93,12 @@ a:hover { color: #000; } +.form-search > ul.nav > li.module > a:hover{ + color: #fff; +} + .header img { width: 50px; - margin-top: 5px; } .content .methods code { diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 37440006aa..918b483bfe 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -11,12 +11,10 @@ using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Web.BackOffice.AspNetCore; -using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Extensions; using Umbraco.Web.Website.AspNetCore; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; - namespace Umbraco.Web.UI.BackOffice { public class Startup diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 46dc20034b..95fdaeec67 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -116,6 +116,16 @@ "Replacement": "" } ] + }, + "Global": { + "Smtp": { + "From": "noreply@example.com", + "Host": "127.0.0.1", + "Port": 25, + "UserName": "username", + "Password": "password", + "DeliveryMethod": "network" + } } } } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 9d2602ad6b..5e66765379 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 85eaa34319..a876b1a6b7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -8,7 +8,8 @@ Tilføj domæne Revisionsspor Gennemse elementer - Skift dokumenttype + Skift Dokument Type + Skift Input Type Kopier Opret Eksportér @@ -246,7 +247,7 @@ Dette dokument er udgivet, men dets URL kan ikke dirigeres Udgiv Udgivet - Udgivet (Afventende ændringer) + Udgivet (Ventede ændringer) Udgivelsesstatus Udgiv med undersider for at udgive %0% og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]> Udgiv med undersider for at udgive de valgte sprog og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]> @@ -425,6 +426,7 @@ Internt link: Ved lokalt link, indsæt da en "#" foran linket Åben i nyt vindue? + Makroindstillinger Denne makro har ingen egenskaber du kan redigere Indsæt tekst Rediger rettigheder for @@ -479,6 +481,7 @@ Fjern link fra dit konto Vælg editor + Vælg konfiguration Vælg snippet Dette vil slette noden og alle dets sprog. Hvis du kun vil slette et sprog, så afpublicér det i stedet. @@ -661,6 +664,7 @@ Ikon Id Importer + Inkludér undermapper i søgning Søg kun i denne mappe Info Indre margen @@ -780,6 +784,7 @@ Generelt Editor Skift tillad sprogvarianter + Skift tillad segmentering Baggrundsfarve @@ -1378,9 +1383,11 @@ Mange hilsner fra Umbraco robotten Indholdstypen bliver brugt i en komposition og kan derfor ikke blive anvendt som komposition Der er ingen indholdstyper tilgængelige at bruge som komposition Når du fjerner en komposition vil alle associerede indholdsdata blive slettet. Når først dokumenttypen er gemt, er der ingen vej tilbage. - Tilgængelige editors + Opret ny indstilling Genbrug - Editor indstillinger + Input indstillinger + Tilgængelige indstillinger + Opret ny indstilling Konfiguration Ja, slet blev flyttet til @@ -1406,9 +1413,16 @@ Mange hilsner fra Umbraco robotten fane har ingen sorteringsrækkefølge Hvor er denne komposition brugt? Denne komposition brugt i kompositionen af de følgende indholdstyper: - Tillad sprogvariation + Tillad variationer + Tillad sprogvariation + Tillad segmentering + Tillader sprogvariationer + Tillader segmentering Tillad at redaktører kan oprette indhold af denne type på flere sprog. + Tillad at redaktører kan oprette dette indhold på flere sprog. + Tillad at redaktører kan oprette flere udgaver af denne type indhold. Tillad sprogvariation + Tillad segmentering Element-type Er en Element-type En Element-type er tiltænkt brug i f.eks. Nested Content, ikke i indholdstræet. @@ -1737,6 +1751,12 @@ Mange hilsner fra Umbraco robotten Åben backoffice søgning Åben/Luk backoffice hjælp Åben/Luk dine profil indstillinger + Tilføj domæne på %0% + Opret ny node under %0% + Opsæt offentlig adgang på %0% + Opsæt rettigheder på %0% + Juster soterings rækkefølgen for %0% + Opret indholds skabelon baseret på %0% Aktivt sprog Skift sprog til Opret ny mappe diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 9457ea9271..e96ef754c4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -141,6 +141,7 @@ Save and send for approval Save list view Schedule + Preview Save and preview Preview is disabled because there's no template assigned Choose style @@ -168,6 +169,7 @@ Content sent for publishing Content sent for publishing for languages: %0% Sort child items performed by user + %0% Copy Publish Publish @@ -180,6 +182,7 @@ Send To Publish Send To Publish Sort + Custom History (all variants) @@ -250,8 +253,8 @@ Published Published (pending changes) Publication Status - Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> - Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> + %0% and all content items underneath and thereby making their content publicly available.]]> + Publish at Unpublish at Clear Date @@ -288,7 +291,7 @@ Add another text box Remove this text box Content root - Include drafts: also publish unpublished content items. + Include drafts and unpublished content items. This value is hidden. If you need access to view this value please contact your website administrator. This value is hidden. What languages would you like to publish? All languages with content are saved! @@ -302,7 +305,17 @@ Unpublished Languages Unmodified Languages These languages haven't been created - Ready to Publish? + + All new variants will be saved. + Which variants you would like to publish? + Choose which variants to be saved. + Pick variants to send for approval. + Set scheduled publishing... + Select the variants to unpublish. Unpublishing a mandatory language will unpublish all variants. + The following variants is required for publishing to take place: + + We are not ready to Publish + Ready to publish? Ready to Save? Send for approval Select the date and time to publish and/or unpublish the content item. @@ -674,6 +687,7 @@ Icon Id Import + Include subfolders in search Search only this folder Info Inner margin @@ -796,6 +810,7 @@ General Editor Toggle allow culture variants + Toggle allow segmentation Background colour @@ -1646,9 +1661,16 @@ To manage your website, simply open the Umbraco back office and start adding con tab has no sort order Where is this composition used? This composition is currently used in the composition of the following content types: - Allow varying by culture + Allow variations + Allow vary by culture + Allow segmentation + Vary by culture + Vary by segments Allow editors to create content of this type in different languages. + Allow editors to create content of different languages. + Allow editors to create segments of this content. Allow varying by culture + Allow segmentation Element type Is an Element type An Element type is meant to be used for instance in Nested Content, and not in the tree. @@ -2208,6 +2230,12 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Setup Culture and Hostnames for %0% + Create new node under %0% + Setup Public access on %0% + Setup Permissions on %0% + Change sort order for %0% + Create Content Template based on %0% Open context menu for Current language Switch language to @@ -2223,7 +2251,7 @@ To manage your website, simply open the Umbraco back office and start adding con Create Edit Name - Add new row + Add new row View more options @@ -2395,4 +2423,21 @@ To manage your website, simply open the Umbraco back office and start adding con Umbraco Forms Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + + What are Content Templates? + Content Templates are pre-defined content that can be selected when creating a new content node. + How do I create a Content Template? + + There are two ways to create a Content Template:

+
    +
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • +
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • +
+

Once given a name, editors can start using the Content Template as a foundation for their new page.

+ ]]> +
+ How do I manage Content Templates? + You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it. + 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 4e254d1681..2bb6495977 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -9,6 +9,7 @@ Audit Trail Browse Node Change Document Type + Change Data Type Copy Create Export @@ -139,6 +140,7 @@ Send for approval Save list view Schedule + Preview Save and preview Preview is disabled because there's no template assigned Choose style @@ -169,6 +171,7 @@ Content sent for publishing Content sent for publishing for languages: %0% Sort child items performed by user + %0% Copy Publish Publish @@ -182,6 +185,7 @@ Send To Publish Send To Publish Sort + Custom History (all variants) @@ -253,8 +257,8 @@ Published Published (pending changes)> Publication Status - Publish with descendants to publish %0% and all content items underneath and thereby making their content publicly available.]]> - Publish with descendants to publish the selected languages and the same languages of content items underneath and thereby making their content publicly available.]]> + %0% and all content items underneath and thereby making their content publicly available.]]> + Publish at Unpublish at Clear Date @@ -292,7 +296,7 @@ Add another text box Remove this text box Content root - Include drafts: also publish unpublished content items. + Include drafts and unpublished content items. This value is hidden. If you need access to view this value please contact your website administrator. This value is hidden. What languages would you like to publish? All languages with content are saved! @@ -306,7 +310,17 @@ Unpublished Languages Unmodified Languages These languages haven't been created - Ready to Publish? + + All new variants will be saved. + Which variants you would like to publish? + Choose which variants to be saved. + Pick variants to send for approval. + Set scheduled publishing... + Select the variants to unpublish. Unpublishing a mandatory language will unpublish all variants. + The following variants is required for publishing to take place: + + We are not ready to Publish + Ready to publish? Ready to Save? Send for approval Select the date and time to publish and/or unpublish the content item. @@ -356,7 +370,8 @@ Document Types within the Settings section, by changing the Allow as root option under Permissions.]]> Media Types Types within the Settings section, by editing the Allowed child node types under Permissions.]]> The selected media in the tree doesn't allow for any other media to be created below it. - Edit permissions for this media type Document Type without a template + Edit permissions for this media type + Document Type without a template New folder New data type New JavaScript file @@ -493,6 +508,7 @@ Un-link your account Select editor + Select configuration Select snippet This will delete the node and all its languages. If you only want to delete one language, you should unpublish the node in that language instead. @@ -556,6 +572,9 @@ #value or ?key=value Enter alias... Generating alias... + Create item + Edit + Name Create custom list view @@ -676,6 +695,7 @@ Icon Id Import + Include subfolders in search Search only this folder Info Inner margin @@ -797,6 +817,7 @@ General Editor Toggle allow culture variants + Toggle allow segmentation Background color @@ -1631,6 +1652,8 @@ To manage your website, simply open the Umbraco back office and start adding con Create new Use existing Editor settings + Available configurations + Create a new configuration Configuration Yes, delete was moved underneath @@ -1656,9 +1679,16 @@ To manage your website, simply open the Umbraco back office and start adding con tab has no sort order Where is this composition used? This composition is currently used in the composition of the following content types: - Allow varying by culture + Allow variations + Allow vary by culture + Allow segmentation + Vary by culture + Vary by segments Allow editors to create content of this type in different languages. + Allow editors to create content of different languages. + Allow editors to create segments of this content. Allow varying by culture + Allow segmentation Element type Is an element type An element type is meant to be used for instance in Nested Content, and not in the tree. @@ -2220,6 +2250,12 @@ To manage your website, simply open the Umbraco back office and start adding con Open backoffice search Open/Close backoffice help Open/Close your profile options + Setup Culture and Hostnames for %0% + Create new node under %0% + Setup Public access on %0% + Setup Permissions on %0% + Change sort order for %0% + Create Content Template based on %0% Open context menu for Current language Switch language to @@ -2407,4 +2443,21 @@ To manage your website, simply open the Umbraco back office and start adding con Umbraco Forms Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it! + + What are Content Templates? + Content Templates are pre-defined content that can be selected when creating a new content node. + How do I create a Content Template? + + There are two ways to create a Content Template:

+
    +
  • Right-click a content node and select "Create Content Template" to create a new Content Template.
  • +
  • Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.
  • +
+

Once given a name, editors can start using the Content Template as a foundation for their new page.

+ ]]> +
+ How do I manage Content Templates? + You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it. + diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index 7b3f2a2184..d0aa1a1c34 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -14,6 +14,10 @@ "content": "

Thank you for using Umbraco! Would you like to stay up-to-date with Umbraco product updates, security advisories, community news and special offers? Sign up for our newsletter and never miss out on the latest Umbraco news.

By signing up, you agree that we can use your info according to our privacy policy.

", "view": "emails", "type": "promotion" + }, + { + "title": "Thank you for subscribing to our mailing list", + "view": "confirm" } ] }, diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs new file mode 100644 index 0000000000..5d1aa8cb85 --- /dev/null +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.PropertyEditors; + +namespace Umbraco.Web.Compose +{ + public class NestedContentPropertyComponent : IComponent + { + public void Initialize() + { + ContentService.Copying += ContentService_Copying; + ContentService.Saving += ContentService_Saving; + } + + private void ContentService_Copying(IContentService sender, CopyEventArgs e) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + UpdateNestedContentProperties(nestedContentProps, false); + } + + private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) + { + // One or more content nodes could be saved in a bulk publish + foreach (var entity in e.SavedEntities) + { + // When a content node contains nested content property + // Check if the copied node contains a nested content + var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + UpdateNestedContentProperties(nestedContentProps, true); + } + } + + public void Terminate() + { + ContentService.Copying -= ContentService_Copying; + ContentService.Saving -= ContentService_Saving; + } + + private void UpdateNestedContentProperties(IEnumerable nestedContentProps, bool onlyMissingKeys) + { + // Each NC Property on a doctype + foreach (var nestedContentProp in nestedContentProps) + { + // A NC Prop may have one or more values due to cultures + var propVals = nestedContentProp.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested NC's + var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested NC's + var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), onlyMissingKeys); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + + + // internal for tests + internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null) + { + // used so we can test nicely + if (createGuid == null) + createGuid = () => Guid.NewGuid(); + + if (string.IsNullOrWhiteSpace(rawJson) || !rawJson.DetectIsJson()) + return rawJson; + + // Parse JSON + var complexEditorValue = JToken.Parse(rawJson); + + UpdateNestedContentKeysRecursively(complexEditorValue, onlyMissingKeys, createGuid); + + return complexEditorValue.ToString(); + } + + private void UpdateNestedContentKeysRecursively(JToken json, bool onlyMissingKeys, Func createGuid) + { + // check if this is NC + var isNestedContent = json.SelectTokens($"$..['{NestedContentPropertyEditor.ContentTypeAliasPropertyKey}']", false).Any(); + + // select all values (flatten) + var allProperties = json.SelectTokens("$..*").OfType().Select(x => x.Parent as JProperty).WhereNotNull().ToList(); + foreach (var prop in allProperties) + { + if (prop.Name == NestedContentPropertyEditor.ContentTypeAliasPropertyKey) + { + // get it's sibling 'key' property + var ncKeyVal = prop.Parent["key"] as JValue; + // TODO: This bool seems odd, if the key is null, shouldn't we fill it in regardless of onlyMissingKeys? + if ((onlyMissingKeys && ncKeyVal == null) || (!onlyMissingKeys && ncKeyVal != null)) + { + // create or replace + prop.Parent["key"] = createGuid().ToString(); + } + } + else if (!isNestedContent || prop.Name != "key") + { + // this is an arbitrary property that could contain a nested complex editor + var propVal = prop.Value?.ToString(); + // check if this might contain a nested NC + if (!propVal.IsNullOrWhiteSpace() && propVal.DetectIsJson() && propVal.InvariantContains(NestedContentPropertyEditor.ContentTypeAliasPropertyKey)) + { + // recurse + var parsed = JToken.Parse(propVal); + UpdateNestedContentKeysRecursively(parsed, onlyMissingKeys, createGuid); + // set the value to the updated one + prop.Value = parsed.ToString(); + } + } + } + } + + } +} diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs new file mode 100644 index 0000000000..4c9d9dee1c --- /dev/null +++ b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs @@ -0,0 +1,9 @@ +using Umbraco.Core; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Compose +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] + public class NestedContentPropertyComposer : ComponentComposer, ICoreComposer + { } +} diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 29a13ea6aa..01e13ff051 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -1,15 +1,15 @@ -using System; +using System; using System.Linq; using System.Net; using System.Net.Http; using System.Collections.Generic; +using System.Net.Mail; using System.Security.Principal; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Mvc; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; +using Microsoft.AspNetCore.Identity; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; @@ -48,6 +48,7 @@ namespace Umbraco.Web.Editors private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtimeState; private readonly ISecuritySettings _securitySettings; + private readonly IEmailSender _emailSender; public AuthenticationController( IUserPasswordConfiguration passwordConfiguration, @@ -61,13 +62,15 @@ namespace Umbraco.Web.Editors IRuntimeState runtimeState, UmbracoMapper umbracoMapper, ISecuritySettings securitySettings, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoMapper, publishedUrlProvider) { _passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); _securitySettings = securitySettings ?? throw new ArgumentNullException(nameof(securitySettings)); + _emailSender = emailSender; } protected BackOfficeUserManager UserManager => _userManager @@ -105,15 +108,15 @@ namespace Umbraco.Web.Editors if (decoded.IsNullOrWhiteSpace()) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var identityUser = await UserManager.FindByIdAsync(id); + var identityUser = await UserManager.FindByIdAsync(id.ToString()); if (identityUser == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var result = await UserManager.ConfirmEmailAsync(id, decoded); + var result = await UserManager.ConfirmEmailAsync(identityUser, decoded); if (result.Succeeded == false) { - throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(string.Join(", ", result.Errors))); + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(result.Errors.ToErrorMessage())); } Request.TryGetOwinContext().Result.Authentication.SignOut( @@ -131,13 +134,16 @@ namespace Umbraco.Web.Editors [ValidateAngularAntiForgeryToken] public async Task PostUnLinkLogin(UnLinkLoginModel unlinkLoginModel) { + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user == null) throw new InvalidOperationException("Could not find user"); + var result = await UserManager.RemoveLoginAsync( - User.Identity.GetUserId(), - new UserLoginInfo(unlinkLoginModel.LoginProvider, unlinkLoginModel.ProviderKey)); + user, + unlinkLoginModel.LoginProvider, + unlinkLoginModel.ProviderKey); if (result.Succeeded) { - var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); await SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false); return Request.CreateResponse(HttpStatusCode.OK); } @@ -225,7 +231,7 @@ namespace Umbraco.Web.Editors [ValidateAngularAntiForgeryToken] public async Task> GetCurrentUserLinkedLogins() { - var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId().ResultOr(0)); + var identityUser = await UserManager.FindByIdAsync(UmbracoContext.Security.GetUserId().ResultOr(0).ToString()); return identityUser.Logins.ToDictionary(x => x.LoginProvider, x => x.ProviderKey); } @@ -244,61 +250,58 @@ namespace Umbraco.Web.Editors var result = await SignInManager.PasswordSignInAsync( loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: + // get the user + var user = Services.UserService.GetByUsername(loginModel.Username); + UserManager.RaiseLoginSuccessEvent(user.Id); - // get the user - var user = Services.UserService.GetByUsername(loginModel.Username); - UserManager.RaiseLoginSuccessEvent(user.Id); - - return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User); - case SignInStatus.RequiresVerification: - - var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; - if (twofactorOptions == null) - { - throw new HttpResponseException( - Request.CreateErrorResponse( - HttpStatusCode.BadRequest, - "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); - } - - var twofactorView = twofactorOptions.GetTwoFactorView( - owinContext, - UmbracoContext, - loginModel.Username); - - if (twofactorView.IsNullOrWhiteSpace()) - { - throw new HttpResponseException( - Request.CreateErrorResponse( - HttpStatusCode.BadRequest, - typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); - } - - var attemptedUser = Services.UserService.GetByUsername(loginModel.Username); - - // create a with information to display a custom two factor send code view - var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new - { - twoFactorView = twofactorView, - userId = attemptedUser.Id - }); - - UserManager.RaiseLoginRequiresVerificationEvent(attemptedUser.Id); - - return verifyResponse; - - case SignInStatus.LockedOut: - case SignInStatus.Failure: - default: - // return BadRequest (400), we don't want to return a 401 because that get's intercepted - // by our angular helper because it thinks that we need to re-perform the request once we are - // authorized and we don't want to return a 403 because angular will show a warning message indicating - // that the user doesn't have access to perform this function, we just want to return a normal invalid message. - throw new HttpResponseException(HttpStatusCode.BadRequest); + return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User); } + + if (result.RequiresTwoFactor) + { + var twofactorOptions = UserManager as IUmbracoBackOfficeTwoFactorOptions; + if (twofactorOptions == null) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.BadRequest, + "UserManager does not implement " + typeof(IUmbracoBackOfficeTwoFactorOptions))); + } + + var twofactorView = twofactorOptions.GetTwoFactorView( + owinContext, + UmbracoContext, + loginModel.Username); + + if (twofactorView.IsNullOrWhiteSpace()) + { + throw new HttpResponseException( + Request.CreateErrorResponse( + HttpStatusCode.BadRequest, + typeof(IUmbracoBackOfficeTwoFactorOptions) + ".GetTwoFactorView returned an empty string")); + } + + var attemptedUser = Services.UserService.GetByUsername(loginModel.Username); + + // create a with information to display a custom two factor send code view + var verifyResponse = Request.CreateResponse(HttpStatusCode.PaymentRequired, new + { + twoFactorView = twofactorView, + userId = attemptedUser.Id + }); + + UserManager.RaiseLoginRequiresVerificationEvent(attemptedUser.Id); + + return verifyResponse; + } + + // return BadRequest (400), we don't want to return a 401 because that get's intercepted + // by our angular helper because it thinks that we need to re-perform the request once we are + // authorized and we don't want to return a 403 because angular will show a warning message indicating + // that the user doesn't have access to perform this function, we just want to return a normal invalid message. + throw new HttpResponseException(HttpStatusCode.BadRequest); } /// @@ -315,13 +318,13 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(HttpStatusCode.BadRequest); } - var identityUser = await SignInManager.UserManager.FindByEmailAsync(model.Email); + var identityUser = await UserManager.FindByEmailAsync(model.Email); if (identityUser != null) { var user = Services.UserService.GetByEmail(model.Email); if (user != null) { - var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); + var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser); var callbackUrl = ConstructCallbackUrl(identityUser.Id, code); var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", @@ -329,11 +332,19 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings), new[] { identityUser.UserName, callbackUrl }); - await UserManager.SendEmailAsync(identityUser.Id, - Services.TextService.Localize("login/resetPasswordEmailCopySubject", - // Ensure the culture of the found user is used for the email! - UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)), - message); + var subject = Services.TextService.Localize("login/resetPasswordEmailCopySubject", + // Ensure the culture of the found user is used for the email! + UmbracoUserExtensions.GetUserCulture(identityUser.Culture, Services.TextService, GlobalSettings)); + + var mailMessage = new MailMessage() + { + Subject = subject, + Body = message, + IsBodyHtml = true, + To = { user.Email} + }; + + await _emailSender.SendAsync(mailMessage); UserManager.RaiseForgotPasswordRequestedEvent(user.Id); } @@ -350,12 +361,15 @@ namespace Umbraco.Web.Editors public async Task> Get2FAProviders() { var userId = await SignInManager.GetVerifiedUserIdAsync(); - if (userId == int.MinValue) + if (string.IsNullOrWhiteSpace(userId)) { Logger.Warn("Get2FAProviders :: No verified user found, returning 404"); throw new HttpResponseException(HttpStatusCode.NotFound); } - var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId); + + var user = await UserManager.FindByIdAsync(userId); + var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(user); + return userFactors; } @@ -366,7 +380,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); var userId = await SignInManager.GetVerifiedUserIdAsync(); - if (userId == int.MinValue) + if (string.IsNullOrWhiteSpace(userId)) { Logger.Warn("Get2FAProviders :: No verified user found, returning 404"); throw new HttpResponseException(HttpStatusCode.NotFound); @@ -399,18 +413,19 @@ namespace Umbraco.Web.Editors var owinContext = TryGetOwinContext().Result; var user = Services.UserService.GetByUsername(userName); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: - UserManager.RaiseLoginSuccessEvent(user.Id); - return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User); - case SignInStatus.LockedOut: - UserManager.RaiseAccountLockedEvent(user.Id); - return Request.CreateValidationErrorResponse("User is locked out"); - case SignInStatus.Failure: - default: - return Request.CreateValidationErrorResponse("Invalid code"); + UserManager.RaiseLoginSuccessEvent(user.Id); + return SetPrincipalAndReturnUserDetail(user, owinContext.Request.User); } + + if (result.IsLockedOut) + { + UserManager.RaiseAccountLockedEvent(user.Id); + return Request.CreateValidationErrorResponse("User is locked out"); + } + + return Request.CreateValidationErrorResponse("Invalid code"); } /// @@ -420,25 +435,27 @@ namespace Umbraco.Web.Editors [SetAngularAntiForgeryTokens] public async Task PostSetPassword(SetPasswordModel model) { - var result = await UserManager.ResetPasswordAsync(model.UserId, model.ResetCode, model.Password); + var identityUser = await UserManager.FindByIdAsync(model.UserId.ToString()); + + var result = await UserManager.ResetPasswordAsync(identityUser, model.ResetCode, model.Password); if (result.Succeeded) { - var lockedOut = await UserManager.IsLockedOutAsync(model.UserId); + var lockedOut = await UserManager.IsLockedOutAsync(identityUser); if (lockedOut) { Logger.Info("User {UserId} is currently locked out, unlocking and resetting AccessFailedCount", model.UserId); //// var user = await UserManager.FindByIdAsync(model.UserId); - var unlockResult = await UserManager.SetLockoutEndDateAsync(model.UserId, DateTimeOffset.Now); + var unlockResult = await UserManager.SetLockoutEndDateAsync(identityUser, DateTimeOffset.Now); if (unlockResult.Succeeded == false) { - Logger.Warn("Could not unlock for user {UserId} - error {UnlockError}", model.UserId, unlockResult.Errors.First()); + Logger.Warn("Could not unlock for user {UserId} - error {UnlockError}", model.UserId, unlockResult.Errors.First().Description); } - var resetAccessFailedCountResult = await UserManager.ResetAccessFailedCountAsync(model.UserId); + var resetAccessFailedCountResult = await UserManager.ResetAccessFailedCountAsync(identityUser); if (resetAccessFailedCountResult.Succeeded == false) { - Logger.Warn("Could not reset access failed count {UserId} - error {UnlockError}", model.UserId, unlockResult.Errors.First()); + Logger.Warn("Could not reset access failed count {UserId} - error {UnlockError}", model.UserId, unlockResult.Errors.First().Description); } } @@ -446,13 +463,11 @@ namespace Umbraco.Web.Editors // if user was only invited, then they have not been approved // but a successful forgot password flow (e.g. if their token had expired and they did a forgot password instead of request new invite) // means we have verified their email - if (!UserManager.IsEmailConfirmed(model.UserId)) + if (!await UserManager.IsEmailConfirmedAsync(identityUser)) { - await UserManager.ConfirmEmailAsync(model.UserId, model.ResetCode); + await UserManager.ConfirmEmailAsync(identityUser, model.ResetCode); } - // if the user is invited, enable their account on forgot password - var identityUser = await UserManager.FindByIdAsync(model.UserId); // invited is not approved, never logged in, invited date present /* if (LastLoginDate == default && IsApproved == false && InvitedDate != null) @@ -474,7 +489,7 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } return Request.CreateValidationErrorResponse( - result.Errors.Any() ? result.Errors.First() : "Set password failed"); + result.Errors.Any() ? result.Errors.First().Description : "Set password failed"); } @@ -561,9 +576,8 @@ namespace Umbraco.Web.Editors { foreach (var error in result.Errors) { - ModelState.AddModelError(prefix, error); + ModelState.AddModelError(prefix, error.Description); } } - } } diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 587e6c5ea3..7e3638d96a 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -8,8 +8,7 @@ using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.UI; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; +using Microsoft.AspNetCore.Identity; using Microsoft.Owin.Security; using Newtonsoft.Json; using Umbraco.Core; @@ -148,25 +147,19 @@ namespace Umbraco.Web.Editors } var id = parts[0]; - int intId; - if (int.TryParse(id, out intId) == false) - { - Logger.Warn("VerifyUser endpoint reached with invalid token: {Invite}", invite); - return RedirectToAction("Default"); - } - var identityUser = await UserManager.FindByIdAsync(intId); + var identityUser = await UserManager.FindByIdAsync(id); if (identityUser == null) { Logger.Warn("VerifyUser endpoint reached with non existing user: {UserId}", id); return RedirectToAction("Default"); } - var result = await UserManager.ConfirmEmailAsync(intId, decoded); + var result = await UserManager.ConfirmEmailAsync(identityUser, decoded); if (result.Succeeded == false) { - Logger.Warn("Could not verify email, Error: {Errors}, Token: {Invite}", string.Join(",", result.Errors), invite); + Logger.Warn("Could not verify email, Error: {Errors}, Token: {Invite}", result.Errors.ToErrorMessage(), invite); return new RedirectResult(Url.Action("Default") + "#/login/false?invite=3"); } @@ -304,10 +297,10 @@ namespace Umbraco.Web.Editors [HttpGet] public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) { - var user = UserManager.FindById(userId); + var user = await UserManager.FindByIdAsync(userId.ToString()); if (user != null) { - var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", resetCode, UserManager, user); + var result = await UserManager.VerifyUserTokenAsync(user, "ResetPassword", "ResetPassword", resetCode); if (result) { //Add a flag and redirect for it to be displayed @@ -335,7 +328,11 @@ namespace Umbraco.Web.Editors return RedirectToLocal(Url.Action("Default", "BackOffice")); } - var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login); + var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); + if (user == null) throw new InvalidOperationException("Could not find user"); + + var result = await UserManager.AddLoginAsync(user, + new UserLoginInfo(loginInfo.Login.LoginProvider, loginInfo.Login.ProviderKey, loginInfo.Login.LoginProvider)); if (result.Succeeded) { return RedirectToLocal(Url.Action("Default", "BackOffice")); @@ -399,7 +396,7 @@ namespace Umbraco.Web.Editors } // Sign in the user with this external login provider if the user already has a login - var user = await UserManager.FindAsync(loginInfo.Login); + var user = await UserManager.FindByLoginAsync(loginInfo.Login.LoginProvider, loginInfo.Login.ProviderKey); if (user != null) { // TODO: It might be worth keeping some of the claims associated with the ExternalLoginInfo, in which case we @@ -490,21 +487,22 @@ namespace Umbraco.Web.Editors if (userCreationResult.Succeeded == false) { - ViewData.SetExternalSignInError(userCreationResult.Errors); + ViewData.SetExternalSignInError(userCreationResult.Errors.Select(x => x.Description).ToList()); } else { - var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); + var linkResult = await UserManager.AddLoginAsync(autoLinkUser, + new UserLoginInfo(loginInfo.Login.LoginProvider, loginInfo.Login.ProviderKey, loginInfo.Login.LoginProvider)); if (linkResult.Succeeded == false) { - ViewData.SetExternalSignInError(linkResult.Errors); + ViewData.SetExternalSignInError(linkResult.Errors.Select(x => x.Description).ToList()); //If this fails, we should really delete the user since it will be in an inconsistent state! var deleteResult = await UserManager.DeleteAsync(autoLinkUser); if (deleteResult.Succeeded == false) { //DOH! ... this isn't good, combine all errors to be shown - ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors)); + ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList()); } } else diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 058f787324..274fb34a33 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Runtime; using Umbraco.Core.WebAssets; +using Umbraco.Web.Security; namespace Umbraco.Web.Editors { @@ -370,6 +371,7 @@ namespace Umbraco.Web.Editors {"loginBackgroundImage", _contentSettings.LoginBackgroundImage}, {"showUserInvite", EmailSender.CanSendRequiredEmail(globalSettings)}, {"canSendRequiredEmail", EmailSender.CanSendRequiredEmail(globalSettings)}, + {"showAllowSegmentationForDocumentTypes", false}, } }, { diff --git a/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs b/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs index 04dcfab665..a2df34f569 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs @@ -51,7 +51,19 @@ namespace Umbraco.Web.Editors.Binders } } - // TODO: anything after 3 parts we can put in metadata + //if there are 4 parts part 4 is always segment + string segment = null; + if (parts.Length > 3) + { + segment = parts[3]; + //normalize to null if empty + if (segment.IsNullOrWhiteSpace()) + { + segment = null; + } + } + + // TODO: anything after 4 parts we can put in metadata var fileName = file.Headers.ContentDisposition.FileName.Trim('\"'); @@ -60,6 +72,7 @@ namespace Umbraco.Web.Editors.Binders TempFilePath = file.LocalFileName, PropertyAlias = propAlias, Culture = culture, + Segment = segment, FileName = fileName }); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 2ea1ec3a24..c1ddb6dba8 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -34,6 +34,7 @@ using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; using Umbraco.Web.Routing; +using Umbraco.Core.Collections; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -720,12 +721,19 @@ namespace Umbraco.Web.Editors { if (variantCount > 1) { - var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors); - foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray()) + var variantErrors = ModelState.GetVariantsWithErrors(cultureForInvariantErrors); + + var validVariants = contentItem.Variants + .Where(x => x.Save && !variantErrors.Contains((x.Culture, x.Segment))) + .Select(x => (culture: x.Culture, segment: x.Segment)); + + foreach (var (culture, segment) in validVariants) { - AddSuccessNotification(notifications, c, + var variantName = GetVariantName(culture, segment); + + AddSuccessNotification(notifications, culture, segment, Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editVariantSendToPublishText", new[] { _allLangs.Value[c].CultureName })); + Services.TextService.Localize("speechBubbles/editVariantSendToPublishText", new[] { variantName })); } } else if (ModelState.IsValid) @@ -856,7 +864,7 @@ namespace Umbraco.Web.Editors //if there's more than 1 variant, then we need to add the culture specific error //messages based on the variants in error so that the messages show in the publish/save dialog if (variants.Count > 1) - AddCultureValidationError(variant.Culture, "publish/contentPublishedFailedByMissingName"); + AddVariantValidationError(variant.Culture, variant.Segment, "publish/contentPublishedFailedByMissingName"); else return false; //It's invariant and is missing critical data, it cannot be saved } @@ -910,12 +918,19 @@ namespace Umbraco.Web.Editors { if (variantCount > 1) { - var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors); - foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray()) + var variantErrors = ModelState.GetVariantsWithErrors(cultureForInvariantErrors); + + var savedWithoutErrors = contentItem.Variants + .Where(x => x.Save && !variantErrors.Contains((x.Culture, x.Segment))) + .Select(x => (culture: x.Culture, segment: x.Segment)); + + foreach (var (culture, segment) in savedWithoutErrors) { - AddSuccessNotification(notifications, c, + var variantName = GetVariantName(culture, segment); + + AddSuccessNotification(notifications, culture, segment, Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize(variantSavedLocalizationKey, new[] { _allLangs.Value[c].CultureName })); + Services.TextService.Localize(variantSavedLocalizationKey, new[] { variantName })); } } else if (ModelState.IsValid) @@ -1048,14 +1063,16 @@ namespace Umbraco.Web.Editors if (!isPublished && releaseDates.Count == 0) { //can't continue, a mandatory variant is not published and not scheduled for publishing - AddCultureValidationError(culture, "speechBubbles/scheduleErrReleaseDate2"); + // TODO: Add segment + AddVariantValidationError(culture, null, "speechBubbles/scheduleErrReleaseDate2"); isValid = false; continue; } if (!isPublished && releaseDates.Any(x => nonMandatoryVariantReleaseDates.Any(r => x.Date > r.Date))) { //can't continue, a mandatory variant is not published and it's scheduled for publishing after a non-mandatory - AddCultureValidationError(culture, "speechBubbles/scheduleErrReleaseDate3"); + // TODO: Add segment + AddVariantValidationError(culture, null, "speechBubbles/scheduleErrReleaseDate3"); isValid = false; continue; } @@ -1069,7 +1086,7 @@ namespace Umbraco.Web.Editors //1) release date cannot be less than now if (variant.ReleaseDate.HasValue && variant.ReleaseDate < DateTime.Now) { - AddCultureValidationError(variant.Culture, "speechBubbles/scheduleErrReleaseDate1"); + AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/scheduleErrReleaseDate1"); isValid = false; continue; } @@ -1077,7 +1094,7 @@ namespace Umbraco.Web.Editors //2) expire date cannot be less than now if (variant.ExpireDate.HasValue && variant.ExpireDate < DateTime.Now) { - AddCultureValidationError(variant.Culture, "speechBubbles/scheduleErrExpireDate1"); + AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/scheduleErrExpireDate1"); isValid = false; continue; } @@ -1085,7 +1102,7 @@ namespace Umbraco.Web.Editors //3) expire date cannot be less than release date if (variant.ExpireDate.HasValue && variant.ReleaseDate.HasValue && variant.ExpireDate <= variant.ReleaseDate) { - AddCultureValidationError(variant.Culture, "speechBubbles/scheduleErrExpireDate2"); + AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/scheduleErrExpireDate2"); isValid = false; continue; } @@ -1110,12 +1127,13 @@ namespace Umbraco.Web.Editors /// global notifications will be shown if all variant processing is successful and the save/publish dialog is closed, otherwise /// variant specific notifications are used to show success messages in the save/publish dialog. /// - private static void AddSuccessNotification(IDictionary notifications, string culture, string header, string msg) + private static void AddSuccessNotification(IDictionary notifications, string culture, string segment, string header, string msg) { //add the global notification (which will display globally if all variants are successfully processed) notifications[string.Empty].AddSuccessNotification(header, msg); //add the variant specific notification (which will display in the dialog if all variants are not successfully processed) - notifications.GetOrCreate(culture).AddSuccessNotification(header, msg); + var key = culture + "_" + segment; + notifications.GetOrCreate(key).AddSuccessNotification(header, msg); } /// @@ -1165,17 +1183,16 @@ namespace Umbraco.Web.Editors return publishStatus; } - //All variants in this collection should have a culture if we get here! but we'll double check and filter here - var cultureVariants = contentItem.Variants.Where(x => !x.Culture.IsNullOrWhiteSpace()).ToList(); - var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList(); - var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors); + var variantErrors = ModelState.GetVariantsWithErrors(cultureForInvariantErrors); + + var variants = contentItem.Variants.ToList(); //validate if we can publish based on the mandatory language requirements var canPublish = ValidatePublishingMandatoryLanguages( - cultureErrors, - contentItem, cultureVariants, mandatoryCultures, + variantErrors, + contentItem, variants, mandatoryCultures, mandatoryVariant => mandatoryVariant.Publish); //Now check if there are validation errors on each variant. @@ -1185,11 +1202,11 @@ namespace Umbraco.Web.Editors foreach (var variant in contentItem.Variants) { - if (cultureErrors.Contains(variant.Culture)) + if (variantErrors.Contains((variant.Culture, variant.Segment))) variant.Publish = false; } - var culturesToPublish = cultureVariants.Where(x => x.Publish).Select(x => x.Culture).ToArray(); + var culturesToPublish = variants.Where(x => x.Publish).Select(x => x.Culture).ToArray(); if (canPublish) { @@ -1237,17 +1254,16 @@ namespace Umbraco.Web.Editors return publishStatus; } - //All variants in this collection should have a culture if we get here! but we'll double check and filter here - var cultureVariants = contentItem.Variants.Where(x => !x.Culture.IsNullOrWhiteSpace()).ToList(); - var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList(); - var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors); + var variantErrors = ModelState.GetVariantsWithErrors(cultureForInvariantErrors); + + var variants = contentItem.Variants.ToList(); //validate if we can publish based on the mandatory languages selected var canPublish = ValidatePublishingMandatoryLanguages( - cultureErrors, - contentItem, cultureVariants, mandatoryCultures, + variantErrors, + contentItem, variants, mandatoryCultures, mandatoryVariant => mandatoryVariant.Publish); //if none are published and there are validation errors for mandatory cultures, then we can't publish anything @@ -1259,19 +1275,19 @@ namespace Umbraco.Web.Editors //It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages. foreach (var variant in contentItem.Variants) { - if (cultureErrors.Contains(variant.Culture)) + if (variantErrors.Contains((variant.Culture, variant.Segment))) variant.Publish = false; } //At this stage all variants might have failed validation which means there are no cultures flagged for publishing! - var culturesToPublish = cultureVariants.Where(x => x.Publish).Select(x => x.Culture).ToArray(); + var culturesToPublish = variants.Where(x => x.Publish).Select(x => x.Culture).ToArray(); canPublish = canPublish && culturesToPublish.Length > 0; if (canPublish) { //try to publish all the values on the model - this will generally only fail if someone is tampering with the request //since there's no reason variant rules would be violated in normal cases. - canPublish = PublishCulture(contentItem.PersistedContent, cultureVariants, defaultCulture); + canPublish = PublishCulture(contentItem.PersistedContent, variants, defaultCulture); } if (canPublish) @@ -1296,16 +1312,16 @@ namespace Umbraco.Web.Editors /// /// Validate if publishing is possible based on the mandatory language requirements /// - /// + /// /// - /// + /// /// /// /// private bool ValidatePublishingMandatoryLanguages( - IReadOnlyCollection culturesWithValidationErrors, + IReadOnlyCollection<(string culture, string segment)> variantsWithValidationErrors, ContentItemSave contentItem, - IReadOnlyCollection cultureVariants, + IReadOnlyCollection variants, IReadOnlyList mandatoryCultures, Func publishingCheck) { @@ -1316,11 +1332,11 @@ namespace Umbraco.Web.Editors { //Check if a mandatory language is missing from being published - var mandatoryVariant = cultureVariants.First(x => x.Culture.InvariantEquals(culture)); + var mandatoryVariant = variants.First(x => x.Culture.InvariantEquals(culture)); var isPublished = contentItem.PersistedContent.Published && contentItem.PersistedContent.IsCulturePublished(culture); var isPublishing = isPublished || publishingCheck(mandatoryVariant); - var isValid = !culturesWithValidationErrors.InvariantContains(culture); + var isValid = !variantsWithValidationErrors.Select(v => v.culture).InvariantContains(culture); result.Add((mandatoryVariant, isPublished || isPublishing, isValid)); } @@ -1335,19 +1351,19 @@ namespace Umbraco.Web.Editors if (r.publishing && !r.isValid) { //flagged for publishing but the mandatory culture is invalid - AddCultureValidationError(r.model.Culture, "publish/contentPublishedFailedReqCultureValidationError"); + AddVariantValidationError(r.model.Culture, r.model.Segment, "publish/contentPublishedFailedReqCultureValidationError"); canPublish = false; } else if (r.publishing && r.isValid && firstInvalidMandatoryCulture != null) { //in this case this culture also cannot be published because another mandatory culture is invalid - AddCultureValidationError(r.model.Culture, "publish/contentPublishedFailedReqCultureValidationError", firstInvalidMandatoryCulture); + AddVariantValidationError(r.model.Culture, r.model.Segment, "publish/contentPublishedFailedReqCultureValidationError", firstInvalidMandatoryCulture); canPublish = false; } else if (!r.publishing) { //cannot continue publishing since a required culture that is not currently being published isn't published - AddCultureValidationError(r.model.Culture, "speechBubbles/contentReqCulturePublishError"); + AddVariantValidationError(r.model.Culture, r.model.Segment, "speechBubbles/contentReqCulturePublishError"); canPublish = false; } } @@ -1372,7 +1388,7 @@ namespace Umbraco.Web.Editors var valid = persistentContent.PublishCulture(CultureImpact.Explicit(variant.Culture, defaultCulture.InvariantEquals(variant.Culture))); if (!valid) { - AddCultureValidationError(variant.Culture, "speechBubbles/contentCultureValidationError"); + AddVariantValidationError(variant.Culture, variant.Segment, "speechBubbles/contentCultureValidationError"); return false; } } @@ -1384,14 +1400,40 @@ namespace Umbraco.Web.Editors /// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs /// /// Culture to assign the error to + /// Segment to assign the error to /// /// /// The culture used in the localization message, null by default which means will be used. /// - private void AddCultureValidationError(string culture, string localizationKey, string cultureToken = null) + private void AddVariantValidationError(string culture, string segment, string localizationKey, string cultureToken = null) { - var errMsg = Services.TextService.Localize(localizationKey, new[] { cultureToken == null ? _allLangs.Value[culture].CultureName : _allLangs.Value[cultureToken].CultureName }); - ModelState.AddCultureValidationError(culture, errMsg); + var cultureToUse = cultureToken ?? culture; + var variantName = GetVariantName(cultureToUse, segment); + + var errMsg = Services.TextService.Localize(localizationKey, new[] { variantName }); + + ModelState.AddVariantValidationError(culture, segment, errMsg); + } + + /// + /// Creates the human readable variant name based on culture and segment + /// + /// Culture + /// Segment + /// + private string GetVariantName(string culture, string segment) + { + if(culture.IsNullOrWhiteSpace() && segment.IsNullOrWhiteSpace()) + { + // TODO: Get name for default variant from somewhere? + return "Default"; + } + + var cultureName = culture == null ? null : _allLangs.Value[culture].CultureName; + var variantName = string.Join(" — ", new[] { segment, cultureName }.Where(x => !x.IsNullOrWhiteSpace())); + + // Format: [—] + return variantName; } /// @@ -1833,11 +1875,11 @@ namespace Umbraco.Web.Editors if (!ModelState.IsValid && display.Variants.Count() > 1) { //Add any culture specific errors here - var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors); + var variantErrors = ModelState.GetVariantsWithErrors(cultureForInvariantErrors); - foreach (var cultureError in cultureErrors) + foreach (var (culture, segment) in variantErrors) { - AddCultureValidationError(cultureError, "speechBubbles/contentCultureValidationError"); + AddVariantValidationError(culture, segment, "speechBubbles/contentCultureValidationError"); } } @@ -1889,7 +1931,8 @@ namespace Umbraco.Web.Editors ? variant.PropertyCollectionDto : new ContentPropertyCollectionDto { - Properties = variant.PropertyCollectionDto.Properties.Where(x => !x.Culture.IsNullOrWhiteSpace()) + Properties = variant.PropertyCollectionDto.Properties.Where( + x => !x.Culture.IsNullOrWhiteSpace() || !x.Segment.IsNullOrWhiteSpace()) }; //for each variant, map the property values diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index a7949084b5..b23cf57643 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -93,7 +93,7 @@ namespace Umbraco.Web.Editors // prepare files, if any matching property and culture var files = contentItem.UploadedFiles - .Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture) + .Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture && x.Segment == propertyDto.Segment) .ToArray(); foreach (var file in files) diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs index 5715d0ca7c..2ad09dc895 100644 --- a/src/Umbraco.Web/Editors/CurrentUserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -158,15 +158,16 @@ namespace Umbraco.Web.Editors [OverrideAuthorization] public async Task PostSetInvitedUserPassword([FromBody]string newPassword) { - var result = await UserManager.AddPasswordAsync(Security.GetUserId().ResultOr(0), newPassword); + var user = await UserManager.FindByIdAsync(Security.GetUserId().ResultOr(0).ToString()); + if (user == null) throw new InvalidOperationException("Could not find user"); + + var result = await UserManager.AddPasswordAsync(user, newPassword); if (result.Succeeded == false) { //it wasn't successful, so add the change error to the model state, we've name the property alias _umb_password on the form // so that is why it is being used here. - ModelState.AddModelError( - "value", - string.Join(", ", result.Errors)); + ModelState.AddModelError("value", result.Errors.ToErrorMessage()); throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 2e1656b45d..bc8590b437 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -144,9 +144,6 @@ namespace Umbraco.Web.Editors try { - //clear and replace - index.CreateIndex(); - var cacheKey = "temp_indexing_op_" + index.Name; //put temp val in cache which is used as a rudimentary way to know when the indexing is done AppCaches.RuntimeCache.Insert(cacheKey, () => "tempValue", TimeSpan.FromMinutes(5)); diff --git a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs index e0ec047918..bd27a872d0 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs @@ -198,7 +198,7 @@ namespace Umbraco.Web.Editors.Filters r.ErrorMessage = property.ValidationRegExpMessage; } - modelState.AddPropertyError(r, property.Alias, property.Culture); + modelState.AddPropertyError(r, property.Alias, property.Culture, property.Segment); } } } diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs index 179ef8fb45..b6757f76cc 100644 --- a/src/Umbraco.Web/Editors/PasswordChanger.cs +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -44,6 +44,13 @@ namespace Umbraco.Web.Editors return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); } + var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id.ToString()); + if (backOfficeIdentityUser == null) + { + //this really shouldn't ever happen... but just in case + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password could not be verified", new[] { "oldPassword" }) }); + } + //Are we just changing another user's password? if (passwordModel.OldPassword.IsNullOrWhiteSpace()) { @@ -60,13 +67,13 @@ namespace Umbraco.Web.Editors } //ok, we should be able to reset it - var resetToken = await userMgr.GeneratePasswordResetTokenAsync(savingUser.Id); + var resetToken = await userMgr.GeneratePasswordResetTokenAsync(backOfficeIdentityUser); var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, passwordModel.NewPassword); if (resetResult.Succeeded == false) { - var errors = string.Join(". ", resetResult.Errors); + var errors = resetResult.Errors.ToErrorMessage(); _logger.Warn("Could not reset user password {PasswordErrors}", errors); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "value" }) }); } @@ -74,15 +81,6 @@ namespace Umbraco.Web.Editors return Attempt.Succeed(new PasswordChangedModel()); } - //we're changing our own password... - - //get the user - var backOfficeIdentityUser = await userMgr.FindByIdAsync(savingUser.Id); - if (backOfficeIdentityUser == null) - { - //this really shouldn't ever happen... but just in case - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password could not be verified", new[] { "oldPassword" }) }); - } //is the old password correct? var validateResult = await userMgr.CheckPasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword); if (validateResult == false) @@ -91,11 +89,11 @@ namespace Umbraco.Web.Editors return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) }); } //can we change to the new password? - var changeResult = await userMgr.ChangePasswordAsync(savingUser.Id, passwordModel.OldPassword, passwordModel.NewPassword); + var changeResult = await userMgr.ChangePasswordAsync(backOfficeIdentityUser, passwordModel.OldPassword, passwordModel.NewPassword); if (changeResult.Succeeded == false) { //no, fail with error messages for "password" - var errors = string.Join(". ", changeResult.Errors); + var errors = changeResult.Errors.ToErrorMessage(); _logger.Warn("Could not change user password {PasswordErrors}", errors); return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "password" }) }); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 843b7cc583..3c71245a0a 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -4,13 +4,13 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Mail; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Mvc; -using Microsoft.AspNet.Identity; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; @@ -20,7 +20,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; -using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web.Editors.Filters; @@ -38,6 +37,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; using Umbraco.Web.Routing; using Umbraco.Core.Media; +using Umbraco.Web.Security; namespace Umbraco.Web.Editors { @@ -53,6 +53,7 @@ namespace Umbraco.Web.Editors private readonly ISqlContext _sqlContext; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly ISecuritySettings _securitySettings; + private readonly IEmailSender _emailSender; public UsersController( IGlobalSettings globalSettings, @@ -69,7 +70,8 @@ namespace Umbraco.Web.Editors IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, IPublishedUrlProvider publishedUrlProvider, - ISecuritySettings securitySettings) + ISecuritySettings securitySettings, + IEmailSender emailSender) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, shortStringHelper, umbracoMapper, publishedUrlProvider) { _mediaFileSystem = mediaFileSystem; @@ -78,6 +80,7 @@ namespace Umbraco.Web.Editors _sqlContext = sqlContext; _imageUrlGenerator = imageUrlGenerator; _securitySettings = securitySettings; + _emailSender = emailSender; } /// @@ -322,26 +325,21 @@ namespace Umbraco.Web.Editors if (created.Succeeded == false) { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); + Request.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage())); } - //we need to generate a password, however we can only do that if the user manager has a password validator that - //we can read values from - var passwordValidator = UserManager.PasswordValidator as PasswordValidator; - var resetPassword = string.Empty; - if (passwordValidator != null) + string resetPassword; + var password = UserManager.GeneratePassword(); + + var result = await UserManager.AddPasswordAsync(identityUser, password); + if (result.Succeeded == false) { - var password = UserManager.GeneratePassword(); - - var result = await UserManager.AddPasswordAsync(identityUser.Id, password); - if (result.Succeeded == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); - } - resetPassword = password; + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage())); } + resetPassword = password; + //now re-look the user back up which will now exist var user = Services.UserService.GetByEmail(userSave.Email); @@ -416,7 +414,7 @@ namespace Umbraco.Web.Editors if (created.Succeeded == false) { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse(string.Join(", ", created.Errors))); + Request.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage())); } //now re-look the user back up @@ -476,7 +474,8 @@ namespace Umbraco.Web.Editors private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message) { - var token = await UserManager.GenerateEmailConfirmationTokenAsync((int)userDisplay.Id); + var user = await UserManager.FindByIdAsync(((int) userDisplay.Id).ToString()); + var token = await UserManager.GenerateEmailConfirmationTokenAsync(user); var inviteToken = string.Format("{0}{1}{2}", (int)userDisplay.Id, @@ -505,16 +504,15 @@ namespace Umbraco.Web.Editors UmbracoUserExtensions.GetUserCulture(to.Language, Services.TextService, GlobalSettings), new[] { userDisplay.Name, from, message, inviteUri.ToString(), fromEmail }); - await UserManager.EmailService.SendAsync( - //send the special UmbracoEmailMessage which configures it's own sender - //to allow for events to handle sending the message if no smtp is configured - new UmbracoEmailMessage(new EmailSender(GlobalSettings, true)) - { - Body = emailBody, - Destination = userDisplay.Email, - Subject = emailSubject - }); + var mailMessage = new MailMessage() + { + Subject = emailSubject, + Body = emailBody, + IsBodyHtml = true, + To = { to.Email} + }; + await _emailSender.SendAsync(mailMessage); } /// @@ -706,34 +704,29 @@ namespace Umbraco.Web.Editors [AdminUsersAuthorize("userIds")] public async Task PostUnlockUsers([FromUri]int[] userIds) { - if (userIds.Length <= 0) - return Request.CreateResponse(HttpStatusCode.OK); - - if (userIds.Length == 1) - { - var unlockResult = await UserManager.SetLockoutEndDateAsync(userIds[0], DateTimeOffset.Now); - if (unlockResult.Succeeded == false) - { - return Request.CreateValidationErrorResponse( - string.Format("Could not unlock for user {0} - error {1}", userIds[0], unlockResult.Errors.First())); - } - var user = await UserManager.FindByIdAsync(userIds[0]); - return Request.CreateNotificationSuccessResponse( - Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name })); - } + if (userIds.Length <= 0) return Request.CreateResponse(HttpStatusCode.OK); foreach (var u in userIds) { - var unlockResult = await UserManager.SetLockoutEndDateAsync(u, DateTimeOffset.Now); + var user = await UserManager.FindByIdAsync(u.ToString()); + if (user == null) throw new InvalidOperationException(); + + var unlockResult = await UserManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now); if (unlockResult.Succeeded == false) { return Request.CreateValidationErrorResponse( - string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.First())); + string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.ToErrorMessage())); + } + + if (userIds.Length == 1) + { + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] {user.Name})); } } return Request.CreateNotificationSuccessResponse( - Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] { userIds.Length.ToString() })); + Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] {userIds.Length.ToString()})); } [AdminUsersAuthorize("userIds")] diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index eefb02ef6d..e680f155ca 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -15,6 +15,7 @@ using Umbraco.Web.Composing; using Umbraco.Web.Editors; using Umbraco.Web.Features; using Umbraco.Web.Models; +using Umbraco.Web.Security; using Umbraco.Web.Trees; using Umbraco.Web.WebAssets; diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index d93fd3a131..fedb93d6f1 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -11,6 +12,7 @@ using Umbraco.Core.Migrations.Install; using Umbraco.Core.Services; using Umbraco.Web.Install.Models; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Security; namespace Umbraco.Web.Install.InstallSteps { @@ -54,9 +56,14 @@ namespace Umbraco.Web.Install.InstallSteps { throw new InvalidOperationException("Could not find the super user!"); } + admin.Email = user.Email.Trim(); + admin.Name = user.Name.Trim(); + admin.Username = user.Email.Trim(); + + _userService.Save(admin); var userManager = _httpContextAccessor.GetRequiredHttpContext().GetOwinContext().GetBackOfficeUserManager(); - var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId); + var membershipUser = await userManager.FindByIdAsync(Constants.Security.SuperUserId.ToString()); if (membershipUser == null) { throw new InvalidOperationException( @@ -64,19 +71,15 @@ namespace Umbraco.Web.Install.InstallSteps } //To change the password here we actually need to reset it since we don't have an old one to use to change - var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser.Id); - var resetResult = - await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim()); + var resetToken = await userManager.GeneratePasswordResetTokenAsync(membershipUser); + if (string.IsNullOrWhiteSpace(resetToken)) + throw new InvalidOperationException("Could not reset password: unable to generate internal reset token"); + + var resetResult = await userManager.ChangePasswordWithResetAsync(membershipUser.Id, resetToken, user.Password.Trim()); if (!resetResult.Succeeded) - { - throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors)); - } + throw new InvalidOperationException("Could not reset password: " + string.Join(", ", resetResult.Errors.ToErrorMessage())); - admin.Email = user.Email.Trim(); - admin.Name = user.Name.Trim(); - admin.Username = user.Email.Trim(); - _userService.Save(admin); if (user.SubscribeToNewsLetter) { diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 00b17781e0..10b1b5dadd 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -49,13 +49,15 @@ namespace Umbraco.Web /// /// The culture for the property, if the property is invariant than this is empty internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, - ValidationResult result, string propertyAlias, string culture = "") + ValidationResult result, string propertyAlias, string culture = "", string segment = "") { if (culture == null) culture = ""; modelState.AddValidationError(result, "_Properties", propertyAlias, //if the culture is null, we'll add the term 'invariant' as part of the key - culture.IsNullOrWhiteSpace() ? "invariant" : culture); + culture.IsNullOrWhiteSpace() ? "invariant" : culture, + // if the segment is null, we'll add the term 'null' as part of the key + segment.IsNullOrWhiteSpace() ? "null" : segment); } /// @@ -63,11 +65,12 @@ namespace Umbraco.Web /// /// /// + /// /// - internal static void AddCultureValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, - string culture, string errMsg) + internal static void AddVariantValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, + string culture, string segment, string errMsg) { - var key = "_content_variant_" + culture + "_"; + var key = "_content_variant_" + (culture.IsNullOrWhiteSpace() ? "invariant" : culture) + "_" + (segment.IsNullOrWhiteSpace() ? "null" : segment) + "_"; if (modelState.ContainsKey(key)) return; modelState.AddModelError(key, errMsg); } @@ -81,23 +84,28 @@ namespace Umbraco.Web /// /// A list of cultures that have property validation errors. The default culture will be returned for any invariant property errors. /// - internal static IReadOnlyList GetCulturesWithPropertyErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, - ILocalizationService localizationService, string cultureForInvariantErrors) + internal static IReadOnlyList<(string culture, string segment)> GetVariantsWithPropertyErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, + string cultureForInvariantErrors) { - //Add any culture specific errors here - var cultureErrors = modelState.Keys + //Add any variant specific errors here + var variantErrors = modelState.Keys + .Where(key => key.StartsWith("_Properties.")) //only choose _Properties errors .Select(x => x.Split('.')) //split into parts - .Where(x => x.Length >= 3 && x[0] == "_Properties") //only choose _Properties errors - .Select(x => x[2]) //select the culture part - .Where(x => !x.IsNullOrWhiteSpace()) //if it has a value - //if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language + .Where(x => x.Length >= 4 && !x[2].IsNullOrWhiteSpace() && !x[3].IsNullOrWhiteSpace()) + .Select(x => (culture: x[2], segment: x[3])) + //if the culture is marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language //so errors for those must show up under the default lang. - .Select(x => x == "invariant" ? cultureForInvariantErrors : x) - .WhereNotNull() + //if the segment is marked "null" then return an actual null + .Select(x => + { + var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture; + var segment = x.segment == "null" ? null : x.segment; + return (culture, segment); + }) .Distinct() .ToList(); - return cultureErrors; + return variantErrors; } /// @@ -109,23 +117,33 @@ namespace Umbraco.Web /// /// A list of cultures that have validation errors. The default culture will be returned for any invariant errors. /// - internal static IReadOnlyList GetCulturesWithErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, - ILocalizationService localizationService, string cultureForInvariantErrors) + internal static IReadOnlyList<(string culture, string segment)> GetVariantsWithErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, string cultureForInvariantErrors) { - var propertyCultureErrors = modelState.GetCulturesWithPropertyErrors(localizationService, cultureForInvariantErrors); + var propertyVariantErrors = modelState.GetVariantsWithPropertyErrors(cultureForInvariantErrors); - //now check the other special culture errors that are - var genericCultureErrors = modelState.Keys + //now check the other special variant errors that are + var genericVariantErrors = modelState.Keys .Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_")) - .Select(x => x.TrimStart("_content_variant_").TrimEnd("_")) - .Where(x => !x.IsNullOrWhiteSpace()) + .Select(x => x.TrimStart("_content_variant_").TrimEnd("_")) + .Select(x => + { + // Format "_" + var cs = x.Split(new[] { '_' }); + return (culture: cs[0], segment: cs[1]); + }) + .Where(x => !x.culture.IsNullOrWhiteSpace()) //if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language //so errors for those must show up under the default lang. - .Select(x => x == "invariant" ? cultureForInvariantErrors : x) - .WhereNotNull() + //if the segment is marked "null" then return an actual null + .Select(x => + { + var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture; + var segment = x.segment == "null" ? null : x.segment; + return (culture, segment); + }) .Distinct(); - return propertyCultureErrors.Union(genericCultureErrors).ToList(); + return propertyVariantErrors.Union(genericVariantErrors).Distinct().ToList(); } /// diff --git a/src/Umbraco.Web/Models/Identity/IdentityUser.cs b/src/Umbraco.Web/Models/Identity/IdentityUser.cs index df104fcafe..7bd077e879 100644 --- a/src/Umbraco.Web/Models/Identity/IdentityUser.cs +++ b/src/Umbraco.Web/Models/Identity/IdentityUser.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Microsoft.AspNet.Identity; using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Models.Identity @@ -13,7 +12,7 @@ namespace Umbraco.Web.Models.Identity /// This class normally exists inside of the EntityFramework library, not sure why MS chose to explicitly put it there but we don't want /// references to that so we will create our own here /// - public class IdentityUser : IUser + public class IdentityUser where TLogin : IIdentityUserLogin //NOTE: Making our role id a string where TRole : IdentityUserRole diff --git a/src/Umbraco.Web/Models/Identity/UserLoginInfoWrapper.cs b/src/Umbraco.Web/Models/Identity/UserLoginInfoWrapper.cs index cd3cd51d3f..336b4c9e72 100644 --- a/src/Umbraco.Web/Models/Identity/UserLoginInfoWrapper.cs +++ b/src/Umbraco.Web/Models/Identity/UserLoginInfoWrapper.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNet.Identity; +using Microsoft.AspNetCore.Identity; using Umbraco.Core.Models.Identity; namespace Umbraco.Web.Models.Identity diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index c1118d0eb0..aa3730f54f 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -66,7 +66,7 @@ namespace Umbraco.Web.Models.Mapping _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(cultureDictionary, localizedTextService, contentTypeBaseServiceProvider); _stateMapper = new ContentSavedStateMapper(); _basicStateMapper = new ContentBasicSavedStateMapper(); - _contentVariantMapper = new ContentVariantMapper(_localizationService); + _contentVariantMapper = new ContentVariantMapper(_localizationService, localizedTextService); } public void DefineMaps(UmbracoMapper mapper) @@ -118,7 +118,7 @@ namespace Umbraco.Web.Models.Mapping target.ContentDto.Properties = context.MapEnumerable(source.Properties); } - // Umbraco.Code.MapAll -Segment -Language + // Umbraco.Code.MapAll -Segment -Language -DisplayName private void Map(IContent source, ContentVariantDisplay target, MapperContext context) { target.CreateDate = source.CreateDate; diff --git a/src/Umbraco.Web/Mvc/ContentModelBinder.cs b/src/Umbraco.Web/Mvc/ContentModelBinder.cs index a66f8e9089..376ce737b4 100644 --- a/src/Umbraco.Web/Mvc/ContentModelBinder.cs +++ b/src/Umbraco.Web/Mvc/ContentModelBinder.cs @@ -1,6 +1,4 @@ using System; -using System.Globalization; -using System.Linq; using System.Text; using System.Web; using System.Web.Mvc; @@ -14,6 +12,7 @@ namespace Umbraco.Web.Mvc /// /// Maps view models, supporting mapping to and from any IPublishedContent or IContentModel. /// + /// Migrated to .NET Core public class ContentModelBinder : DefaultModelBinder, IModelBinderProvider { // use Instance diff --git a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs index 07a3f2a5e3..eb39cb8faa 100644 --- a/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerFactoryExtensions.cs @@ -25,7 +25,10 @@ namespace Umbraco.Web.Mvc //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - return instance?.GetType(); + var controllerType = instance?.GetType(); + factory.ReleaseController(instance); + + return controllerType; } } } diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs index 98ba7f47de..cfc931fc71 100644 --- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -82,7 +82,10 @@ namespace Umbraco.Web.Mvc //we have no choice but to instantiate the controller var instance = factory.CreateController(requestContext, controllerName); - return instance?.GetType(); + var controllerType = instance?.GetType(); + factory.ReleaseController(instance); + + return controllerType; } return GetControllerType(requestContext, controllerName); diff --git a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs index 82c87c0e0b..60ca151ce6 100644 --- a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs +++ b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.Mvc /// /// This is only enabled when running PureLive /// + /// Migrated to .NET Core internal class ModelBindingExceptionFilter : FilterAttribute, IExceptionFilter { private static readonly Regex GetPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled); diff --git a/src/Umbraco.Web/OwinExtensions.cs b/src/Umbraco.Web/OwinExtensions.cs index 685868a76b..52c1187707 100644 --- a/src/Umbraco.Web/OwinExtensions.cs +++ b/src/Umbraco.Web/OwinExtensions.cs @@ -1,11 +1,8 @@ using System; using System.Web; -using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security; using Umbraco.Core; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; using Umbraco.Web.Models.Identity; using Umbraco.Web.Security; @@ -54,7 +51,7 @@ namespace Umbraco.Web var ctx = owinContext.Get(typeof(HttpContextBase).FullName); return ctx == null ? Attempt.Fail() : Attempt.Succeed(ctx); } - + /// /// Gets the back office sign in manager out of OWIN /// @@ -83,6 +80,25 @@ namespace Umbraco.Web return marker.GetManager(owinContext) ?? throw new NullReferenceException($"Could not resolve an instance of {typeof (BackOfficeUserManager)} from the {typeof (IOwinContext)}."); } - } + /// + /// Adapted from Microsoft.AspNet.Identity.Owin.OwinContextExtensions + /// + public static T Get(this IOwinContext context) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + return context.Get(GetKey(typeof(T))); + } + + public static IOwinContext Set(this IOwinContext context, T value) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + return context.Set(GetKey(typeof(T)), value); + } + + private static string GetKey(Type t) + { + return "AspNet.Identity.Owin:" + t.AssemblyQualifiedName; + } + } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 5b59b632eb..5fee84e895 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -108,7 +108,6 @@ namespace Umbraco.Web.Runtime composition.WithCollectionBuilder() .Add(umbracoApiControllerTypes); - // add all known factories, devs can then modify this list on application // startup either by binding to events or in their own global.asax composition.FilteredControllerFactory() diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs index 97cc96d81b..3f129f941d 100644 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Threading; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Owin; using Microsoft.Owin.Extensions; using Microsoft.Owin.Logging; @@ -15,11 +15,8 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; using Umbraco.Core.Mapping; -using Umbraco.Core.Models.Identity; using Umbraco.Net; -using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.Models.Identity; @@ -35,18 +32,10 @@ namespace Umbraco.Web.Security /// /// Configure Default Identity User Manager for Umbraco /// - /// - /// - /// - /// - /// - /// - /// public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, ServiceContext services, - UmbracoMapper mapper, - IContentSettings contentSettings, IGlobalSettings globalSettings, + UmbracoMapper mapper, // TODO: This could probably be optional? IPasswordConfiguration passwordConfiguration, IIpResolver ipResolver) @@ -56,35 +45,28 @@ namespace Umbraco.Web.Security //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( (options, owinContext) => BackOfficeUserManager.Create( - options, services.UserService, services.EntityService, services.ExternalLoginService, - mapper, - contentSettings, globalSettings, + mapper, passwordConfiguration, - ipResolver)); + ipResolver, + new IdentityErrorDescriber(), + app.GetDataProtectionProvider(), + new NullLogger>())); app.SetBackOfficeUserManagerType(); //Create a sign in manager per request - app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger())); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(context, globalSettings, app.CreateLogger())); } /// /// Configure a custom UserStore with the Identity User Manager for Umbraco /// - /// - /// - /// - /// - /// - /// - /// public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, IRuntimeState runtimeState, - IContentSettings contentSettings, IGlobalSettings globalSettings, BackOfficeUserStore customUserStore, // TODO: This could probably be optional? @@ -97,44 +79,17 @@ namespace Umbraco.Web.Security //Configure Umbraco user manager to be created per request app.CreatePerOwinContext( (options, owinContext) => BackOfficeUserManager.Create( - options, - customUserStore, - contentSettings, passwordConfiguration, ipResolver, - globalSettings)); + customUserStore, + new IdentityErrorDescriber(), + app.GetDataProtectionProvider(), + new NullLogger>())); app.SetBackOfficeUserManagerType(); //Create a sign in manager per request - app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); - } - - /// - /// Configure a custom BackOfficeUserManager for Umbraco - /// - /// - /// - /// - /// - public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app, - IRuntimeState runtimeState, - IGlobalSettings globalSettings, - Func, IOwinContext, TManager> userManager) - where TManager : BackOfficeUserManager - where TUser : BackOfficeIdentityUser - { - if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState)); - if (userManager == null) throw new ArgumentNullException(nameof(userManager)); - - //Configure Umbraco user manager to be created per request - app.CreatePerOwinContext(userManager); - - app.SetBackOfficeUserManagerType(); - - //Create a sign in manager per request - app.CreatePerOwinContext( - (options, context) => BackOfficeSignInManager.Create(options, context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); + app.CreatePerOwinContext((options, context) => BackOfficeSignInManager.Create(context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName))); } /// @@ -197,11 +152,11 @@ namespace Umbraco.Web.Security // Enables the application to validate the security stamp when the user // logs in. This is a security feature which is used when you // change a password or add an external login to your account. - OnValidateIdentity = SecurityStampValidator - .OnValidateIdentity( + OnValidateIdentity = UmbracoSecurityStampValidator + .OnValidateIdentity( TimeSpan.FromMinutes(30), - (manager, user) => manager.GenerateUserIdentityAsync(user), - identity => identity.GetUserId()), + (signInManager, manager, user) => signInManager.CreateUserIdentityAsync(user), + identity => identity.GetUserId()), }; @@ -464,5 +419,41 @@ namespace Umbraco.Web.Security return authOptions; } + public static IAppBuilder CreatePerOwinContext(this IAppBuilder app, Func createCallback) + where T : class, IDisposable + { + return CreatePerOwinContext(app, (options, context) => createCallback()); + } + + public static IAppBuilder CreatePerOwinContext(this IAppBuilder app, + Func, IOwinContext, T> createCallback) where T : class, IDisposable + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + return app.CreatePerOwinContext(createCallback, (options, instance) => instance.Dispose()); + } + + public static IAppBuilder CreatePerOwinContext(this IAppBuilder app, + Func, IOwinContext, T> createCallback, + Action, T> disposeCallback) where T : class, IDisposable + { + if (app == null) throw new ArgumentNullException(nameof(app)); + if (createCallback == null) throw new ArgumentNullException(nameof(createCallback)); + if (disposeCallback == null) throw new ArgumentNullException(nameof(disposeCallback)); + + app.Use(typeof(IdentityFactoryMiddleware>), + new IdentityFactoryOptions + { + DataProtectionProvider = app.GetDataProtectionProvider(), + Provider = new IdentityFactoryProvider + { + OnCreate = createCallback, + OnDispose = disposeCallback + } + }); + return app; + } } } diff --git a/src/Umbraco.Web/Security/AuthenticationExtensions.cs b/src/Umbraco.Web/Security/AuthenticationExtensions.cs index ef3140bf6b..7d76eaa7be 100644 --- a/src/Umbraco.Web/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Web/Security/AuthenticationExtensions.cs @@ -7,7 +7,7 @@ using System.Security.Claims; using System.Security.Principal; using System.Threading; using System.Web; -using Microsoft.AspNet.Identity; +using Microsoft.AspNetCore.Identity; using Microsoft.Owin; using Microsoft.Owin.Security; using Newtonsoft.Json; @@ -384,5 +384,11 @@ namespace Umbraco.Web.Security /// private static readonly ConcurrentDictionary UserCultures = new ConcurrentDictionary(); + public static string ToErrorMessage(this IEnumerable errors) + { + if (errors == null) throw new ArgumentNullException(nameof(errors)); + return string.Join(", ", errors.Select(x => x.Description).ToList()); + } + } } diff --git a/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs b/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs index 5da9c77d6b..6ce1bbc86b 100644 --- a/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs +++ b/src/Umbraco.Web/Security/AuthenticationManagerExtensions.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; +using Microsoft.AspNetCore.Identity; using Microsoft.Owin.Security; +using Umbraco.Core; namespace Umbraco.Web.Security { @@ -26,11 +27,12 @@ namespace Umbraco.Web.Security { name = name.Replace(" ", ""); } + var email = result.Identity.FindFirstValue(ClaimTypes.Email); return new ExternalLoginInfo { ExternalIdentity = result.Identity, - Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value), + Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value, idClaim.Issuer), DefaultUserName = name, Email = email }; @@ -76,11 +78,39 @@ namespace Umbraco.Web.Security /// public static async Task GetExternalLoginInfoAsync(this IAuthenticationManager manager, string authenticationType) { - if (manager == null) - { - throw new ArgumentNullException("manager"); - } + if (manager == null) throw new ArgumentNullException(nameof(manager)); return GetExternalLoginInfo(await manager.AuthenticateAsync(authenticationType)); } + + public static IEnumerable GetExternalAuthenticationTypes(this IAuthenticationManager manager) + { + if (manager == null) throw new ArgumentNullException(nameof(manager)); + return manager.GetAuthenticationTypes(d => d.Properties != null && d.Caption != null); + } + + public static ClaimsIdentity CreateTwoFactorRememberBrowserIdentity(this IAuthenticationManager manager, string userId) + { + if (manager == null) throw new ArgumentNullException(nameof(manager)); + + var rememberBrowserIdentity = new ClaimsIdentity(Constants.Web.TwoFactorRememberBrowserCookie); + rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId)); + + return rememberBrowserIdentity; + } + } + + public class ExternalLoginInfo + { + /// Associated login data + public UserLoginInfo Login { get; set; } + + /// Suggested user name for a user + public string DefaultUserName { get; set; } + + /// Email claim from the external identity + public string Email { get; set; } + + /// The external identity + public ClaimsIdentity ExternalIdentity { get; set; } } } diff --git a/src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs b/src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs deleted file mode 100644 index 487e16539b..0000000000 --- a/src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNet.Identity; -using Umbraco.Core; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; -using Umbraco.Web.Models.Identity; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Security -{ - public class BackOfficeClaimsIdentityFactory : ClaimsIdentityFactory - where T: BackOfficeIdentityUser - { - public BackOfficeClaimsIdentityFactory() - { - SecurityStampClaimType = Constants.Security.SessionIdClaimType; - UserNameClaimType = ClaimTypes.Name; - } - - /// - /// Create a ClaimsIdentity from a user - /// - /// - /// - public override async Task CreateAsync(UserManager manager, T user, string authenticationType) - { - var baseIdentity = await base.CreateAsync(manager, user, authenticationType); - - var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity, - user.Id, - user.UserName, - user.Name, - user.CalculatedContentStartNodeIds, - user.CalculatedMediaStartNodeIds, - user.Culture, - //NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written - Guid.NewGuid().ToString(), - user.SecurityStamp, - user.AllowedSections, - user.Roles.Select(x => x.RoleId).ToArray()); - - return umbracoIdentity; - } - } - - public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory - { } -} diff --git a/src/Umbraco.Web/Security/BackOfficeClaimsPrincipalFactory.cs b/src/Umbraco.Web/Security/BackOfficeClaimsPrincipalFactory.cs new file mode 100644 index 0000000000..fe22981831 --- /dev/null +++ b/src/Umbraco.Web/Security/BackOfficeClaimsPrincipalFactory.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Umbraco.Core.Security; +using Umbraco.Web.Models.Identity; + +namespace Umbraco.Web.Security +{ + public class BackOfficeClaimsPrincipalFactory : UserClaimsPrincipalFactory + where TUser : BackOfficeIdentityUser + { + private const string _identityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider"; + private const string _identityProviderClaimValue = "ASP.NET Identity"; + + public BackOfficeClaimsPrincipalFactory(UserManager userManager, IOptions optionsAccessor) + : base(userManager, optionsAccessor) + { + } + + public override async Task CreateAsync(TUser user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + var baseIdentity = await base.GenerateClaimsAsync(user); + + // Required by ASP.NET 4.x anti-forgery implementation + baseIdentity.AddClaim(new Claim(_identityProviderClaimType, _identityProviderClaimValue)); + + var umbracoIdentity = new UmbracoBackOfficeIdentity( + baseIdentity, + user.Id, + user.UserName, + user.Name, + user.CalculatedContentStartNodeIds, + user.CalculatedMediaStartNodeIds, + user.Culture, + //NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written + Guid.NewGuid().ToString(), + user.SecurityStamp, + user.AllowedSections, + user.Roles.Select(x => x.RoleId).ToArray()); + + return new ClaimsPrincipal(umbracoIdentity); + } + } +} diff --git a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs index 8290b8f9b7..01f8dc4e96 100644 --- a/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs +++ b/src/Umbraco.Web/Security/BackOfficeCookieAuthenticationProvider.cs @@ -1,7 +1,6 @@ using System; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; @@ -58,7 +57,7 @@ namespace Umbraco.Web.Security if (context?.OwinContext?.Authentication?.User?.Identity != null) { var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity; - var sessionId = claimsIdentity.FindFirstValue(Core.Constants.Security.SessionIdClaimType); + var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out var guidSession)) { _userService.ClearLoginSession(guidSession); diff --git a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs index 6e0ffac590..bbb4328fc3 100644 --- a/src/Umbraco.Web/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeSignInManager.cs @@ -2,47 +2,63 @@ using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; using Microsoft.Owin; using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; using Umbraco.Web.Models.Identity; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Security { - // TODO: In v8 we need to change this to use an int? nullable TKey instead, see notes against overridden TwoFactorSignInAsync - public class BackOfficeSignInManager : SignInManager + /// + /// Custom sign in manager due to SignInManager not being .NET Standard. + /// Code ported from Umbraco's BackOfficeSignInManager. + /// Can be removed once the web project moves to .NET Core. + /// + public class BackOfficeSignInManager : IDisposable { + private readonly BackOfficeUserManager _userManager; + private readonly IUserClaimsPrincipalFactory _claimsPrincipalFactory; + private readonly IAuthenticationManager _authenticationManager; private readonly ILogger _logger; - private readonly IOwinRequest _request; private readonly IGlobalSettings _globalSettings; + private readonly IOwinRequest _request; - public BackOfficeSignInManager(UserManager userManager, IAuthenticationManager authenticationManager, ILogger logger, IGlobalSettings globalSettings, IOwinRequest request) - : base(userManager, authenticationManager) + public BackOfficeSignInManager( + BackOfficeUserManager userManager, + IUserClaimsPrincipalFactory claimsPrincipalFactory, + IAuthenticationManager authenticationManager, + ILogger logger, + IGlobalSettings globalSettings, + IOwinRequest request) { - if (logger == null) throw new ArgumentNullException("logger"); - if (request == null) throw new ArgumentNullException("request"); - _logger = logger; - _request = request; - _globalSettings = globalSettings; - AuthenticationType = Constants.Security.BackOfficeAuthenticationType; + _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); + _claimsPrincipalFactory = claimsPrincipalFactory ?? throw new ArgumentNullException(nameof(claimsPrincipalFactory)); + _authenticationManager = authenticationManager ?? throw new ArgumentNullException(nameof(authenticationManager)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); + _request = request ?? throw new ArgumentNullException(nameof(request)); } - public override Task CreateUserIdentityAsync(BackOfficeIdentityUser user) + public async Task CreateUserIdentityAsync(BackOfficeIdentityUser user) { - return ((BackOfficeUserManager)UserManager).GenerateUserIdentityAsync(user); + if (user == null) throw new ArgumentNullException(nameof(user)); + + var claimsPrincipal = await _claimsPrincipalFactory.CreateAsync(user); + return claimsPrincipal.Identity as ClaimsIdentity; } - public static BackOfficeSignInManager Create(IdentityFactoryOptions options, IOwinContext context, IGlobalSettings globalSettings, ILogger logger) + public static BackOfficeSignInManager Create(IOwinContext context, IGlobalSettings globalSettings, ILogger logger) { + var userManager = context.GetBackOfficeUserManager(); + return new BackOfficeSignInManager( - context.GetBackOfficeUserManager(), + userManager, + new BackOfficeClaimsPrincipalFactory(userManager, new OptionsWrapper(userManager.Options)), context.Authentication, logger, globalSettings, @@ -54,30 +70,33 @@ namespace Umbraco.Web.Security /// /// /// - public override async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) + public async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) { var result = await PasswordSignInAsyncImpl(userName, password, isPersistent, shouldLockout); - switch (result) + if (result.Succeeded) { - case SignInStatus.Success: - _logger.WriteCore(TraceEventType.Information, 0, - $"User: {userName} logged in from IP address {_request.RemoteIpAddress}", null, null); - break; - case SignInStatus.LockedOut: - _logger.WriteCore(TraceEventType.Information, 0, - $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, the user is locked", null, null); - break; - case SignInStatus.RequiresVerification: - _logger.WriteCore(TraceEventType.Information, 0, - $"Login attempt requires verification for username {userName} from IP address {_request.RemoteIpAddress}", null, null); - break; - case SignInStatus.Failure: - _logger.WriteCore(TraceEventType.Information, 0, - $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}", null, null); - break; - default: - throw new ArgumentOutOfRangeException(); + _logger.WriteCore(TraceEventType.Information, 0, + $"User: {userName} logged in from IP address {_request.RemoteIpAddress}", null, null); + } + else if (result.IsLockedOut) + { + _logger.WriteCore(TraceEventType.Information, 0, + $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, the user is locked", null, null); + } + else if (result.RequiresTwoFactor) + { + _logger.WriteCore(TraceEventType.Information, 0, + $"Login attempt requires verification for username {userName} from IP address {_request.RemoteIpAddress}", null, null); + } + else if (!result.Succeeded || result.IsNotAllowed) + { + _logger.WriteCore(TraceEventType.Information, 0, + $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}", null, null); + } + else + { + throw new ArgumentOutOfRangeException(); } return result; @@ -91,27 +110,21 @@ namespace Umbraco.Web.Security /// /// /// - private async Task PasswordSignInAsyncImpl(string userName, string password, bool isPersistent, bool shouldLockout) + private async Task PasswordSignInAsyncImpl(string userName, string password, bool isPersistent, bool shouldLockout) { - if (UserManager == null) - { - return SignInStatus.Failure; - } - - var user = await UserManager.FindByNameAsync(userName); + var user = await _userManager.FindByNameAsync(userName); //if the user is null, create an empty one which can be used for auto-linking - if (user == null) - user = BackOfficeIdentityUser.CreateNew(_globalSettings, userName, null, _globalSettings.DefaultUILanguage); + if (user == null) user = BackOfficeIdentityUser.CreateNew(_globalSettings, userName, null, _globalSettings.DefaultUILanguage); //check the password for the user, this will allow a developer to auto-link //an account if they have specified an IBackOfficeUserPasswordChecker - if (await UserManager.CheckPasswordAsync(user, password)) + if (await _userManager.CheckPasswordAsync(user, password)) { //the underlying call to this will query the user by Id which IS cached! - if (await UserManager.IsLockedOutAsync(user.Id)) + if (await _userManager.IsLockedOutAsync(user)) { - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } // We need to verify that the user belongs to one or more groups that define content and media start nodes. @@ -125,11 +138,11 @@ namespace Umbraco.Web.Security $"Login attempt failed for username {userName} from IP address {_request.RemoteIpAddress}, no content and/or media start nodes could be found for any of the user's groups", null, null); // We will say its a sucessful login which it is, but they have no node access - return SignInStatus.Success; + return SignInResult.Success; } } - await UserManager.ResetAccessFailedCountAsync(user.Id); + await _userManager.ResetAccessFailedCountAsync(user); return await SignInOrTwoFactor(user, isPersistent); } @@ -138,19 +151,18 @@ namespace Umbraco.Web.Security if (user.HasIdentity && shouldLockout) { // If lockout is requested, increment access failed count which might lock out the user - await UserManager.AccessFailedAsync(user.Id); - if (await UserManager.IsLockedOutAsync(user.Id)) + await _userManager.AccessFailedAsync(user); + if (await _userManager.IsLockedOutAsync(user)) { //at this point we've just locked the user out after too many failed login attempts if (requestContext != null) { var backofficeUserManager = requestContext.GetBackOfficeUserManager(); - if (backofficeUserManager != null) - backofficeUserManager.RaiseAccountLockedEvent(user.Id); + if (backofficeUserManager != null) backofficeUserManager.RaiseAccountLockedEvent(user.Id); } - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } } @@ -161,7 +173,7 @@ namespace Umbraco.Web.Security backofficeUserManager.RaiseInvalidLoginAttemptEvent(userName); } - return SignInStatus.Failure; + return SignInResult.Failed; } /// @@ -170,20 +182,20 @@ namespace Umbraco.Web.Security /// /// /// - private async Task SignInOrTwoFactor(BackOfficeIdentityUser user, bool isPersistent) + private async Task SignInOrTwoFactor(BackOfficeIdentityUser user, bool isPersistent) { var id = Convert.ToString(user.Id); - if (await UserManager.GetTwoFactorEnabledAsync(user.Id) - && (await UserManager.GetValidTwoFactorProvidersAsync(user.Id)).Count > 0) + if (await _userManager.GetTwoFactorEnabledAsync(user) + && (await _userManager.GetValidTwoFactorProvidersAsync(user)).Count > 0) { var identity = new ClaimsIdentity(Constants.Security.BackOfficeTwoFactorAuthenticationType); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id)); identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName)); - AuthenticationManager.SignIn(identity); - return SignInStatus.RequiresVerification; + _authenticationManager.SignIn(identity); + return SignInResult.TwoFactorRequired; } await SignInAsync(user, isPersistent, false); - return SignInStatus.Success; + return SignInResult.Success; } /// @@ -193,12 +205,12 @@ namespace Umbraco.Web.Security /// /// /// - public override async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, bool rememberBrowser) + public async Task SignInAsync(BackOfficeIdentityUser user, bool isPersistent, bool rememberBrowser) { var userIdentity = await CreateUserIdentityAsync(user); // Clear any partial cookies from external or two factor partial sign ins - AuthenticationManager.SignOut( + _authenticationManager.SignOut( Constants.Security.BackOfficeExternalAuthenticationType, Constants.Security.BackOfficeTwoFactorAuthenticationType); @@ -206,8 +218,8 @@ namespace Umbraco.Web.Security if (rememberBrowser) { - var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id)); - AuthenticationManager.SignIn(new AuthenticationProperties() + var rememberBrowserIdentity = _authenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id.ToString()); + _authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, AllowRefresh = true, @@ -217,7 +229,7 @@ namespace Umbraco.Web.Security } else { - AuthenticationManager.SignIn(new AuthenticationProperties() + _authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, AllowRefresh = true, @@ -231,7 +243,7 @@ namespace Umbraco.Web.Security if (user.AccessFailedCount > 0) //we have successfully logged in, reset the AccessFailedCount user.AccessFailedCount = 0; - await UserManager.UpdateAsync(user); + await _userManager.UpdateAsync(user); //set the current request's principal to the identity just signed in! _request.User = new ClaimsPrincipal(userIdentity); @@ -250,14 +262,14 @@ namespace Umbraco.Web.Security /// /// Replaces the underlying call which is not flexible and doesn't support a custom cookie /// - public new async Task GetVerifiedUserIdAsync() + public async Task GetVerifiedUserIdAsync() { - var result = await AuthenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); + var result = await _authenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); if (result != null && result.Identity != null && string.IsNullOrEmpty(result.Identity.GetUserId()) == false) { - return ConvertIdFromString(result.Identity.GetUserId()); + return result.Identity.GetUserId(); } - return int.MinValue; + return null; } /// @@ -266,14 +278,14 @@ namespace Umbraco.Web.Security /// public async Task GetVerifiedUserNameAsync() { - var result = await AuthenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); + var result = await _authenticationManager.AuthenticateAsync(Constants.Security.BackOfficeTwoFactorAuthenticationType); if (result != null && result.Identity != null && string.IsNullOrEmpty(result.Identity.GetUserName()) == false) { return result.Identity.GetUserName(); } return null; } - + /// /// Two factor verification step /// @@ -288,32 +300,33 @@ namespace Umbraco.Web.Security /// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate /// all of this code to check for int.MinValue /// - public override async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser) + public async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser) { var userId = await GetVerifiedUserIdAsync(); - if (userId == int.MinValue) + if (string.IsNullOrWhiteSpace(userId)) { - return SignInStatus.Failure; + return SignInResult.Failed; } - var user = await UserManager.FindByIdAsync(userId); + var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { - return SignInStatus.Failure; + return SignInResult.Failed; } - if (await UserManager.IsLockedOutAsync(user.Id)) + if (await _userManager.IsLockedOutAsync(user)) { - return SignInStatus.LockedOut; + return SignInResult.LockedOut; } - if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code)) + if (await _userManager.VerifyTwoFactorTokenAsync(user, provider, code)) { // When token is verified correctly, clear the access failed count used for lockout - await UserManager.ResetAccessFailedCountAsync(user.Id); + await _userManager.ResetAccessFailedCountAsync(user); await SignInAsync(user, isPersistent, rememberBrowser); - return SignInStatus.Success; + return SignInResult.Success; } + // If the token is incorrect, record the failure which also may cause the user to be locked out - await UserManager.AccessFailedAsync(user.Id); - return SignInStatus.Failure; + await _userManager.AccessFailedAsync(user); + return SignInResult.Failed; } /// Send a two factor code to a user @@ -325,15 +338,14 @@ namespace Umbraco.Web.Security /// the default(int) value returned by the base class is always a valid user (i.e. the admin) so we just have to duplicate /// all of this code to check for int.MinVale instead. /// - public override async Task SendTwoFactorCodeAsync(string provider) + public Task SendTwoFactorCodeAsync(string provider) { - var userId = await GetVerifiedUserIdAsync(); - if (userId == int.MinValue) - return false; + throw new NotImplementedException(); + } - var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider); - var identityResult = await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token); - return identityResult.Succeeded; + public void Dispose() + { + _userManager?.Dispose(); } } } diff --git a/src/Umbraco.Web/Security/BackOfficeUserManager.cs b/src/Umbraco.Web/Security/BackOfficeUserManager.cs index 7de7ba37e5..a143029327 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManager.cs @@ -1,218 +1,187 @@ using System; +using System.Collections.Generic; using System.Security.Claims; +using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.Owin.Security.DataProtection; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Mapping; -using Umbraco.Core.Models.Identity; -using Umbraco.Net; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Net; using Umbraco.Web.Models.Identity; -using IPasswordHasher = Microsoft.AspNet.Identity.IPasswordHasher; namespace Umbraco.Web.Security { - /// - /// Default back office user manager - /// public class BackOfficeUserManager : BackOfficeUserManager { public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; public BackOfficeUserManager( - IUserStore store, - IdentityFactoryOptions options, - IContentSettings contentSettingsConfig, IPasswordConfiguration passwordConfiguration, IIpResolver ipResolver, - IGlobalSettings globalSettings) - : base(store, passwordConfiguration, ipResolver) + IUserStore store, + IOptions optionsAccessor, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IDataProtectionProvider dataProtectionProvider, + ILogger> logger) + : base(passwordConfiguration, ipResolver, store, optionsAccessor, userValidators, passwordValidators, keyNormalizer, errors, null, logger) { - if (options == null) throw new ArgumentNullException("options"); - InitUserManager(this, passwordConfiguration, options.DataProtectionProvider, contentSettingsConfig, globalSettings); + InitUserManager(this, dataProtectionProvider); } - + #region Static Create methods - + /// /// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager /// - /// - /// - /// - /// - /// - /// - /// - /// public static BackOfficeUserManager Create( - IdentityFactoryOptions options, IUserService userService, IEntityService entityService, IExternalLoginService externalLoginService, - UmbracoMapper mapper, - IContentSettings contentSettingsConfig, IGlobalSettings globalSettings, + UmbracoMapper mapper, IPasswordConfiguration passwordConfiguration, - IIpResolver ipResolver) + IIpResolver ipResolver, + IdentityErrorDescriber errors, + IDataProtectionProvider dataProtectionProvider, + ILogger> logger) { - if (options == null) throw new ArgumentNullException("options"); - if (userService == null) throw new ArgumentNullException("userService"); - if (externalLoginService == null) throw new ArgumentNullException("externalLoginService"); - var store = new BackOfficeUserStore(userService, entityService, externalLoginService, globalSettings, mapper); - var manager = new BackOfficeUserManager(store, options, contentSettingsConfig, passwordConfiguration, ipResolver, globalSettings); - return manager; + + return Create( + passwordConfiguration, + ipResolver, + store, + errors, + dataProtectionProvider, + logger); } /// /// Creates a BackOfficeUserManager instance with all default options and a custom BackOfficeUserManager instance /// - /// - /// - /// - /// - /// public static BackOfficeUserManager Create( - IdentityFactoryOptions options, - BackOfficeUserStore customUserStore, - IContentSettings contentSettingsConfig, IPasswordConfiguration passwordConfiguration, IIpResolver ipResolver, - IGlobalSettings globalSettings) + IUserStore customUserStore, + IdentityErrorDescriber errors, + IDataProtectionProvider dataProtectionProvider, + ILogger> logger) { - var manager = new BackOfficeUserManager(customUserStore, options, contentSettingsConfig, passwordConfiguration, ipResolver, globalSettings); - return manager; + var options = new IdentityOptions(); + + // Configure validation logic for usernames + var userValidators = new List> { new BackOfficeUserValidator() }; + options.User.RequireUniqueEmail = true; + + // Configure validation logic for passwords + var passwordValidators = new List> { new PasswordValidator() }; + options.Password.RequiredLength = passwordConfiguration.RequiredLength; + options.Password.RequireNonAlphanumeric = passwordConfiguration.RequireNonLetterOrDigit; + options.Password.RequireDigit = passwordConfiguration.RequireDigit; + options.Password.RequireLowercase = passwordConfiguration.RequireLowercase; + options.Password.RequireUppercase = passwordConfiguration.RequireUppercase; + + // Ensure Umbraco security stamp claim type is used + options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; + options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name; + options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role; + options.ClaimsIdentity.SecurityStampClaimType = Constants.Web.SecurityStampClaimType; + + options.Lockout.AllowedForNewUsers = true; + options.Lockout.MaxFailedAccessAttempts = passwordConfiguration.MaxFailedAccessAttemptsBeforeLockout; + //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked + // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are + // locked out or not. + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30); + + return new BackOfficeUserManager( + passwordConfiguration, + ipResolver, + customUserStore, + new OptionsWrapper(options), + userValidators, + passwordValidators, + new NopLookupNormalizer(), + errors, + dataProtectionProvider, + logger); } + #endregion - - } - /// - /// Generic Back office user manager - /// - public class BackOfficeUserManager : UserManager + public class BackOfficeUserManager : UserManager where T : BackOfficeIdentityUser { private PasswordGenerator _passwordGenerator; - public BackOfficeUserManager(IUserStore store, + public BackOfficeUserManager( IPasswordConfiguration passwordConfiguration, - IIpResolver ipResolver) - : base(store) + IIpResolver ipResolver, + IUserStore store, + IOptions optionsAccessor, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, + IServiceProvider services, + ILogger> logger) + : base(store, optionsAccessor, null, userValidators, passwordValidators, keyNormalizer, errors, services, logger) { - PasswordConfiguration = passwordConfiguration; - IpResolver = ipResolver; + PasswordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration)); + IpResolver = ipResolver ?? throw new ArgumentNullException(nameof(ipResolver)); } - #region What we support do not currently - + #region What we do not currently support // TODO: We could support this - but a user claims will mostly just be what is in the auth cookie - public override bool SupportsUserClaim - { - get { return false; } - } - + public override bool SupportsUserClaim => false; + // TODO: Support this - public override bool SupportsQueryableUsers - { - get { return false; } - } + public override bool SupportsQueryableUsers => false; /// /// Developers will need to override this to support custom 2 factor auth /// - public override bool SupportsUserTwoFactor - { - get { return false; } - } + public override bool SupportsUserTwoFactor => false; // TODO: Support this - public override bool SupportsUserPhoneNumber - { - get { return false; } - } + public override bool SupportsUserPhoneNumber => false; #endregion - public virtual async Task GenerateUserIdentityAsync(T user) - { - // NOTE the authenticationType must match the umbraco one - // defined in CookieAuthenticationOptions.AuthenticationType - var userIdentity = await CreateIdentityAsync(user, Core.Constants.Security.BackOfficeAuthenticationType); - return userIdentity; - } - /// /// Initializes the user manager with the correct options /// - /// - /// - /// - /// - /// protected void InitUserManager( BackOfficeUserManager manager, - IPasswordConfiguration passwordConfig, - IDataProtectionProvider dataProtectionProvider, - IContentSettings contentSettingsConfig, - IGlobalSettings globalSettings) + IDataProtectionProvider dataProtectionProvider) { - // Configure validation logic for usernames - manager.UserValidator = new BackOfficeUserValidator(manager) - { - AllowOnlyAlphanumericUserNames = false, - RequireUniqueEmail = true - }; - - // Configure validation logic for passwords - manager.PasswordValidator = new ConfiguredPasswordValidator(passwordConfig); - - //use a custom hasher based on our membership provider - manager.PasswordHasher = GetDefaultPasswordHasher(passwordConfig); + // use a custom hasher based on our membership provider + PasswordHasher = GetDefaultPasswordHasher(PasswordConfiguration); + // set OWIN data protection token provider as default if (dataProtectionProvider != null) { - manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")) - { - TokenLifespan = TimeSpan.FromDays(3) - }; + manager.RegisterTokenProvider( + TokenOptions.DefaultProvider, + new OwinDataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")) + { + TokenLifespan = TimeSpan.FromDays(3) + }); } - manager.UserLockoutEnabledByDefault = true; - manager.MaxFailedAccessAttemptsBeforeLockout = passwordConfig.MaxFailedAccessAttemptsBeforeLockout; - //NOTE: This just needs to be in the future, we currently don't support a lockout timespan, it's either they are locked - // or they are not locked, but this determines what is set on the account lockout date which corresponds to whether they are - // locked out or not. - manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(30); - - //custom identity factory for creating the identity object for which we auth against in the back office - manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); - - manager.EmailService = new EmailService( - contentSettingsConfig.NotificationEmailAddress, - new EmailSender(globalSettings)); - - //NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to support it - - //// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user - //// You can write your own provider and plug in here. - //manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider - //{ - // MessageFormat = "Your security code is: {0}" - //}); - //manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider - //{ - // Subject = "Security Code", - // BodyFormat = "Your security code is: {0}" - //}); - - //manager.SmsService = new SmsService(); + // register ASP.NET Core Identity token providers + manager.RegisterTokenProvider(TokenOptions.DefaultEmailProvider, new EmailTokenProvider()); + manager.RegisterTokenProvider(TokenOptions.DefaultPhoneProvider, new PhoneNumberTokenProvider()); + manager.RegisterTokenProvider(TokenOptions.DefaultAuthenticatorProvider, new AuthenticatorTokenProvider()); } /// @@ -221,12 +190,11 @@ namespace Umbraco.Web.Security /// /// /// - public virtual async Task ValidateSessionIdAsync(int userId, string sessionId) + public virtual async Task ValidateSessionIdAsync(string userId, string sessionId) { - var userSessionStore = Store as IUserSessionStore; + var userSessionStore = Store as IUserSessionStore; //if this is not set, for backwards compat (which would be super rare), we'll just approve it - if (userSessionStore == null) - return true; + if (userSessionStore == null) return true; return await userSessionStore.ValidateSessionIdAsync(userId, sessionId); } @@ -235,12 +203,12 @@ namespace Umbraco.Web.Security /// This will determine which password hasher to use based on what is defined in config /// /// - protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) + protected virtual IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) { //we can use the user aware password hasher (which will be the default and preferred way) - return new UserAwarePasswordHasher(new PasswordSecurity(passwordConfiguration)); + return new UserAwarePasswordHasher(new PasswordSecurity(passwordConfiguration)); } - + /// /// Gets/sets the default back office user password checker /// @@ -262,20 +230,18 @@ namespace Umbraco.Web.Security /// /// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date /// - /// + /// /// /// /// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values /// - public override async Task IsLockedOutAsync(int userId) + public override async Task IsLockedOutAsync(T user) { - var user = await FindByIdAsync(userId); - if (user == null) - throw new InvalidOperationException("No user found by id " + userId); - if (user.IsApproved == false) - return true; + if (user == null) throw new ArgumentNullException(nameof(user)); - return await base.IsLockedOutAsync(userId); + if (user.IsApproved == false) return true; + + return await base.IsLockedOutAsync(user); } #region Overrides for password logic @@ -325,13 +291,7 @@ namespace Umbraco.Web.Security //use the default behavior return await base.CheckPasswordAsync(user, password); } - - public override Task ResetPasswordAsync(int userId, string token, string newPassword) - { - var result = base.ResetPasswordAsync(userId, token, newPassword); - return result; - } - + /// /// This is a special method that will reset the password but will raise the Password Changed event instead of the reset event /// @@ -343,65 +303,53 @@ namespace Umbraco.Web.Security /// We use this because in the back office the only way an admin can change another user's password without first knowing their password /// is to generate a token and reset it, however, when we do this we want to track a password change, not a password reset /// - public Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) + public async Task ChangePasswordWithResetAsync(int userId, string token, string newPassword) { - var result = base.ResetPasswordAsync(userId, token, newPassword); - if (result.Result.Succeeded) - RaisePasswordChangedEvent(userId); + var user = await base.FindByIdAsync(userId.ToString()); + if (user == null) throw new InvalidOperationException("Could not find user"); + + var result = await base.ResetPasswordAsync(user, token, newPassword); + if (result.Succeeded) RaisePasswordChangedEvent(userId); return result; } - public override Task ChangePasswordAsync(int userId, string currentPassword, string newPassword) + public override async Task ChangePasswordAsync(T user, string currentPassword, string newPassword) { - var result = base.ChangePasswordAsync(userId, currentPassword, newPassword); - if (result.Result.Succeeded) - RaisePasswordChangedEvent(userId); + var result = await base.ChangePasswordAsync(user, currentPassword, newPassword); + if (result.Succeeded) RaisePasswordChangedEvent(user.Id); return result; } - + /// /// Override to determine how to hash the password /// - /// - /// - /// - /// - protected override async Task VerifyPasswordAsync(IUserPasswordStore store, T user, string password) - { - var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher; - if (userAwarePasswordHasher == null) - return await base.VerifyPasswordAsync(store, user, password); - - var hash = await store.GetPasswordHashAsync(user); - return userAwarePasswordHasher.VerifyHashedPassword(user, hash, password) != PasswordVerificationResult.Failed; - } - - /// - /// Override to determine how to hash the password - /// - /// /// /// + /// /// /// /// This method is called anytime the password needs to be hashed for storage (i.e. including when reset password is used) /// - protected override async Task UpdatePassword(IUserPasswordStore passwordStore, T user, string newPassword) + protected override async Task UpdatePasswordHash(T user, string newPassword, bool validatePassword) { user.LastPasswordChangeDateUtc = DateTime.UtcNow; - var userAwarePasswordHasher = PasswordHasher as IUserAwarePasswordHasher; - if (userAwarePasswordHasher == null) - return await base.UpdatePassword(passwordStore, user, newPassword); - var result = await PasswordValidator.ValidateAsync(newPassword); - if (result.Succeeded == false) - return result; + if (validatePassword) + { + var validate = await ValidatePasswordAsync(user, newPassword); + if (!validate.Succeeded) + { + return validate; + } + } - await passwordStore.SetPasswordHashAsync(user, userAwarePasswordHasher.HashPassword(user, newPassword)); + var passwordStore = Store as IUserPasswordStore; + if (passwordStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserPasswordStore<>)); + + var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null; + await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken); await UpdateSecurityStampInternal(user); return IdentityResult.Success; - - } /// @@ -409,22 +357,20 @@ namespace Umbraco.Web.Security /// /// /// - private async Task UpdateSecurityStampInternal(BackOfficeIdentityUser user) + private async Task UpdateSecurityStampInternal(T user) { - if (SupportsUserSecurityStamp == false) - return; - await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp()); + if (SupportsUserSecurityStamp == false) return; + await GetSecurityStore().SetSecurityStampAsync(user, NewSecurityStamp(), CancellationToken.None); } /// /// This is copied from the underlying .NET base class since they decided to not expose it /// /// - private IUserSecurityStampStore GetSecurityStore() + private IUserSecurityStampStore GetSecurityStore() { - var store = Store as IUserSecurityStampStore; - if (store == null) - throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); + var store = Store as IUserSecurityStampStore; + if (store == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserSecurityStampStore<>)); return store; } @@ -439,68 +385,66 @@ namespace Umbraco.Web.Security #endregion - public override async Task SetLockoutEndDateAsync(int userId, DateTimeOffset lockoutEnd) + public override async Task SetLockoutEndDateAsync(T user, DateTimeOffset? lockoutEnd) { - var result = await base.SetLockoutEndDateAsync(userId, lockoutEnd); + if (user == null) throw new ArgumentNullException(nameof(user)); + + var result = await base.SetLockoutEndDateAsync(user, lockoutEnd); // The way we unlock is by setting the lockoutEnd date to the current datetime if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) { - RaiseAccountLockedEvent(userId); + RaiseAccountLockedEvent(user.Id); } else { - RaiseAccountUnlockedEvent(userId); + RaiseAccountUnlockedEvent(user.Id); //Resets the login attempt fails back to 0 when unlock is clicked - await ResetAccessFailedCountAsync(userId); - + await ResetAccessFailedCountAsync(user); } return result; } - public override async Task ResetAccessFailedCountAsync(int userId) + public override async Task ResetAccessFailedCountAsync(T user) { - var lockoutStore = (IUserLockoutStore)Store; - var user = await FindByIdAsync(userId); - if (user == null) - throw new InvalidOperationException("No user found by user id " + userId); + if (user == null) throw new ArgumentNullException(nameof(user)); - var accessFailedCount = await GetAccessFailedCountAsync(user.Id); + var lockoutStore = (IUserLockoutStore)Store; + var accessFailedCount = await GetAccessFailedCountAsync(user); if (accessFailedCount == 0) return IdentityResult.Success; - await lockoutStore.ResetAccessFailedCountAsync(user); + await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); //raise the event now that it's reset - RaiseResetAccessFailedCountEvent(userId); + RaiseResetAccessFailedCountEvent(user.Id); return await UpdateAsync(user); } - - /// /// Overrides the Microsoft ASP.NET user management method /// - /// + /// /// - /// returns a Async Task + /// returns a Async Task /// /// /// Doesn't set fail attempts back to 0 /// - public override async Task AccessFailedAsync(int userId) + public override async Task AccessFailedAsync(T user) { - var lockoutStore = (IUserLockoutStore)Store; - var user = await FindByIdAsync(userId); - if (user == null) - throw new InvalidOperationException("No user found by user id " + userId); + if (user == null) throw new ArgumentNullException(nameof(user)); - var count = await lockoutStore.IncrementAccessFailedCountAsync(user); + var lockoutStore = Store as IUserLockoutStore; + if (lockoutStore == null) throw new NotSupportedException("The current user store does not implement " + typeof(IUserLockoutStore<>)); - if (count >= MaxFailedAccessAttemptsBeforeLockout) + var count = await lockoutStore.IncrementAccessFailedCountAsync(user, CancellationToken.None); + + if (count >= Options.Lockout.MaxFailedAccessAttempts) { - await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(DefaultAccountLockoutTimeSpan)); + await lockoutStore.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), + CancellationToken.None); //NOTE: in normal aspnet identity this would do set the number of failed attempts back to 0 //here we are persisting the value for the back office } @@ -508,8 +452,7 @@ namespace Umbraco.Web.Security var result = await UpdateAsync(user); //Slightly confusing: this will return a Success if we successfully update the AccessFailed count - if (result.Succeeded) - RaiseLoginFailedEvent(userId); + if (result.Succeeded) RaiseLoginFailedEvent(user.Id); return result; } @@ -635,7 +578,5 @@ namespace Umbraco.Web.Security { if (ResetAccessFailedCount != null) ResetAccessFailedCount(this, e); } - } - } diff --git a/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs b/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs index 03477db730..b3eb9da3d5 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserManagerMarker.cs @@ -1,8 +1,5 @@ using System; -using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; -using Umbraco.Core.Models.Identity; -using Umbraco.Core.Security; using Umbraco.Web.Models.Identity; namespace Umbraco.Web.Security diff --git a/src/Umbraco.Web/Security/BackOfficeUserStore.cs b/src/Umbraco.Web/Security/BackOfficeUserStore.cs index 78eeba5eff..6f401163aa 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserStore.cs @@ -2,30 +2,30 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; +using Microsoft.AspNetCore.Identity; +using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models.Identity; -using IUser = Umbraco.Core.Models.Membership.IUser; -using Task = System.Threading.Tasks.Task; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { public class BackOfficeUserStore : DisposableObjectSlim, - IUserStore, - IUserPasswordStore, - IUserEmailStore, - IUserLoginStore, - IUserRoleStore, - IUserSecurityStampStore, - IUserLockoutStore, - IUserTwoFactorStore, - IUserSessionStore + IUserPasswordStore, + IUserEmailStore, + IUserLoginStore, + IUserRoleStore, + IUserSecurityStampStore, + IUserLockoutStore, + IUserTwoFactorStore, + IUserSessionStore // TODO: This would require additional columns/tables for now people will need to implement this on their own //IUserPhoneNumberStore, @@ -61,13 +61,53 @@ namespace Umbraco.Core.Security _disposed = true; } + public Task GetUserIdAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) throw new ArgumentNullException(nameof(user)); + + return Task.FromResult(user.Id.ToString()); + } + + public Task GetUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) throw new ArgumentNullException(nameof(user)); + + return Task.FromResult(user.UserName); + } + + public Task SetUserNameAsync(BackOfficeIdentityUser user, string userName, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (user == null) throw new ArgumentNullException(nameof(user)); + + user.UserName = userName; + return Task.CompletedTask; + } + + public Task GetNormalizedUserNameAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + { + return GetUserNameAsync(user, cancellationToken); + } + + public Task SetNormalizedUserNameAsync(BackOfficeIdentityUser user, string normalizedName, CancellationToken cancellationToken) + { + return SetUserNameAsync(user, normalizedName, cancellationToken); + } + /// /// Insert a new user /// /// + /// /// - public Task CreateAsync(BackOfficeIdentityUser user) + public Task CreateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -75,9 +115,9 @@ namespace Umbraco.Core.Security // with an external provider so we'll just generate one and prefix it, the // prefix will help us determine if the password hasn't actually been specified yet. //this will hash the guid with a salt so should be nicely random - var aspHasher = new PasswordHasher(); + var aspHasher = new PasswordHasher(); var emptyPasswordValue = Constants.Security.EmptyPasswordPrefix + - aspHasher.HashPassword(Guid.NewGuid().ToString("N")); + aspHasher.HashPassword(user, Guid.NewGuid().ToString("N")); var userEntity = new User(_globalSettings, user.Name, user.Email, user.UserName, emptyPasswordValue) { @@ -98,26 +138,22 @@ namespace Umbraco.Core.Security //re-assign id user.Id = userEntity.Id; - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } /// /// Update a user /// /// + /// /// - public async Task UpdateAsync(BackOfficeIdentityUser user) + public async Task UpdateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - var asInt = user.Id.TryConvertTo(); - if (asInt == false) - { - throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); - } - - var found = _userService.GetUserById(asInt.Result); + var found = _userService.GetUserById(user.Id); if (found != null) { // we have to remember whether Logins property is dirty, since the UpdateMemberProperties will reset it. @@ -134,6 +170,8 @@ namespace Umbraco.Core.Security _externalLoginService.SaveUserLogins(found.Id, logins.Select(UserLoginInfoWrapper.Wrap)); } } + + return IdentityResult.Success; } /// @@ -141,40 +179,35 @@ namespace Umbraco.Core.Security /// /// /// - public Task DeleteAsync(BackOfficeIdentityUser user) + public Task DeleteAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - var asInt = user.Id.TryConvertTo(); - if (asInt == false) - { - throw new InvalidOperationException("The user id must be an integer to work with the Umbraco"); - } - - var found = _userService.GetUserById(asInt.Result); + var found = _userService.GetUserById(user.Id); if (found != null) { _userService.Delete(found); } - _externalLoginService.DeleteUserLogins(asInt.Result); + _externalLoginService.DeleteUserLogins(user.Id); - return Task.FromResult(0); + return Task.FromResult(IdentityResult.Success); } /// /// Finds a user /// /// + /// /// - public async Task FindByIdAsync(int userId) + public async Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - var user = _userService.GetUserById(userId); - if (user == null) - { - return null; - } + + var user = _userService.GetUserById(UserIdToInt(userId)); + if (user == null) return null; return await Task.FromResult(AssignLoginsCallback(_mapper.Map(user))); } @@ -183,9 +216,11 @@ namespace Umbraco.Core.Security /// Find a user by name /// /// + /// /// - public async Task FindByNameAsync(string userName) + public async Task FindByNameAsync(string userName, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var user = _userService.GetByUsername(userName); if (user == null) @@ -202,9 +237,11 @@ namespace Umbraco.Core.Security /// Set the user password hash /// /// + /// /// - public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash) + public Task SetPasswordHashAsync(BackOfficeIdentityUser user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); if (passwordHash == null) throw new ArgumentNullException(nameof(passwordHash)); @@ -212,16 +249,18 @@ namespace Umbraco.Core.Security user.PasswordHash = passwordHash; - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Get the user password hash /// /// + /// /// - public Task GetPasswordHashAsync(BackOfficeIdentityUser user) + public Task GetPasswordHashAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -232,9 +271,11 @@ namespace Umbraco.Core.Security /// Returns true if a user has a password set /// /// + /// /// - public Task HasPasswordAsync(BackOfficeIdentityUser user) + public Task HasPasswordAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -245,25 +286,29 @@ namespace Umbraco.Core.Security /// Set the user email /// /// + /// /// - public Task SetEmailAsync(BackOfficeIdentityUser user, string email) + public Task SetEmailAsync(BackOfficeIdentityUser user, string email, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException("email"); + if (email.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(email)); user.Email = email; - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Get the user email /// /// + /// /// - public Task GetEmailAsync(BackOfficeIdentityUser user) + public Task GetEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -274,9 +319,11 @@ namespace Umbraco.Core.Security /// Returns true if the user email is confirmed /// /// + /// /// - public Task GetEmailConfirmedAsync(BackOfficeIdentityUser user) + public Task GetEmailConfirmedAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -287,21 +334,25 @@ namespace Umbraco.Core.Security /// Sets whether the user email is confirmed /// /// + /// /// - public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed) + public Task SetEmailConfirmedAsync(BackOfficeIdentityUser user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); user.EmailConfirmed = confirmed; - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Returns the user associated with this email /// /// + /// /// - public Task FindByEmailAsync(string email) + public Task FindByEmailAsync(string email, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); var user = _userService.GetByEmail(email); var result = user == null @@ -311,13 +362,26 @@ namespace Umbraco.Core.Security return Task.FromResult(AssignLoginsCallback(result)); } + public Task GetNormalizedEmailAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken) + { + return GetEmailAsync(user, cancellationToken); + } + + public Task SetNormalizedEmailAsync(BackOfficeIdentityUser user, string normalizedEmail, CancellationToken cancellationToken) + { + return SetEmailAsync(user, normalizedEmail, cancellationToken); + } + /// /// Adds a user login with the specified provider and key /// - /// + /// + /// + /// /// - public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login) + public Task AddLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); if (login == null) throw new ArgumentNullException(nameof(login)); @@ -327,53 +391,55 @@ namespace Umbraco.Core.Security var userLogin = instance; logins.Add(userLogin); - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Removes the user login with the specified combination if it exists /// - /// + /// + /// + /// + /// /// - public Task RemoveLoginAsync(BackOfficeIdentityUser user, UserLoginInfo login) + public Task RemoveLoginAsync(BackOfficeIdentityUser user, string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (login == null) throw new ArgumentNullException(nameof(login)); - var provider = login.LoginProvider; - var key = login.ProviderKey; - var userLogin = user.Logins.SingleOrDefault((l => l.LoginProvider == provider && l.ProviderKey == key)); - if (userLogin != null) - user.Logins.Remove(userLogin); + var userLogin = user.Logins.SingleOrDefault(l => l.LoginProvider == loginProvider && l.ProviderKey == providerKey); + if (userLogin != null) user.Logins.Remove(userLogin); - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Returns the linked accounts for this user /// /// + /// /// - public Task> GetLoginsAsync(BackOfficeIdentityUser user) + public Task> GetLoginsAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult((IList) - user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey)).ToList()); + user.Logins.Select(l => new UserLoginInfo(l.LoginProvider, l.ProviderKey, l.LoginProvider)).ToList()); } /// /// Returns the user associated with this login /// /// - public Task FindAsync(UserLoginInfo login) + public Task FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); - if (login == null) throw new ArgumentNullException(nameof(login)); //get all logins associated with the login id - var result = _externalLoginService.Find(UserLoginInfoWrapper.Wrap(login)).ToArray(); + var result = _externalLoginService.Find(UserLoginInfoWrapper.Wrap(new UserLoginInfo(loginProvider, providerKey, loginProvider))).ToArray(); if (result.Any()) { //return the first user that matches the result @@ -398,54 +464,62 @@ namespace Umbraco.Core.Security /// /// Adds a user to a role (user group) /// - /// + /// + /// + /// /// - public Task AddToRoleAsync(BackOfficeIdentityUser user, string roleName) + public Task AddToRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - if (roleName == null) throw new ArgumentNullException(nameof(roleName)); - if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(roleName)); + if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); + if (string.IsNullOrWhiteSpace(normalizedRoleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); - var userRole = user.Roles.SingleOrDefault(r => r.RoleId == roleName); + var userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); if (userRole == null) { - user.AddRole(roleName); + user.AddRole(normalizedRoleName); } - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Removes the role (user group) for the user /// - /// + /// + /// + /// /// - public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string roleName) + public Task RemoveFromRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { - ThrowIfDisposed(); - if (user == null) throw new ArgumentNullException(nameof(user)); - if (roleName == null) throw new ArgumentNullException(nameof(roleName)); - if (string.IsNullOrWhiteSpace(roleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(roleName)); + cancellationToken.ThrowIfCancellationRequested(); - var userRole = user.Roles.SingleOrDefault(r => r.RoleId == roleName); + if (user == null) throw new ArgumentNullException(nameof(user)); + if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); + if (string.IsNullOrWhiteSpace(normalizedRoleName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(normalizedRoleName)); + + var userRole = user.Roles.SingleOrDefault(r => r.RoleId == normalizedRoleName); if (userRole != null) { user.Roles.Remove(userRole); } - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Returns the roles (user groups) for this user /// /// + /// /// - public Task> GetRolesAsync(BackOfficeIdentityUser user) + public Task> GetRolesAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult((IList)user.Roles.Select(x => x.RoleId).ToList()); @@ -454,36 +528,64 @@ namespace Umbraco.Core.Security /// /// Returns true if a user is in the role /// - /// + /// + /// + /// /// - public Task IsInRoleAsync(BackOfficeIdentityUser user, string roleName) + public Task IsInRoleAsync(BackOfficeIdentityUser user, string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(roleName)); + return Task.FromResult(user.Roles.Select(x => x.RoleId).InvariantContains(normalizedRoleName)); + } + + /// + /// Lists all users of a given role. + /// + /// + /// Identity Role names are equal to Umbraco UserGroup alias. + /// + public Task> GetUsersInRoleAsync(string normalizedRoleName, CancellationToken cancellationToken = default(CancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + if (normalizedRoleName == null) throw new ArgumentNullException(nameof(normalizedRoleName)); + + var userGroup = _userService.GetUserGroupByAlias(normalizedRoleName); + + var users = _userService.GetAllInGroup(userGroup.Id); + IList backOfficeIdentityUsers = users.Select(x => _mapper.Map(x)).ToList(); + + return Task.FromResult(backOfficeIdentityUsers); } /// /// Set the security stamp for the user /// - /// + /// + /// + /// /// - public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp) + public Task SetSecurityStampAsync(BackOfficeIdentityUser user, string stamp, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); user.SecurityStamp = stamp; - return Task.FromResult(0); + return Task.CompletedTask; } /// /// Get the user security stamp /// /// + /// /// - public Task GetSecurityStampAsync(BackOfficeIdentityUser user) + public Task GetSecurityStampAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); @@ -506,12 +608,17 @@ namespace Umbraco.Core.Security /// /// Sets whether two factor authentication is enabled for the user /// - /// + /// + /// + /// /// - public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled) + public virtual Task SetTwoFactorEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + user.TwoFactorEnabled = false; - return Task.FromResult(0); + return Task.CompletedTask; } /// @@ -519,8 +626,11 @@ namespace Umbraco.Core.Security /// /// /// - public virtual Task GetTwoFactorEnabledAsync(BackOfficeIdentityUser user) + public virtual Task GetTwoFactorEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); + return Task.FromResult(false); } @@ -530,42 +640,53 @@ namespace Umbraco.Core.Security /// Returns the DateTimeOffset that represents the end of a user's lockout, any time in the past should be considered not locked out. /// /// + /// /// /// /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status /// - public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user) + public Task GetLockoutEndDateAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return user.LockoutEndDateUtc.HasValue - ? Task.FromResult(DateTimeOffset.MaxValue) - : Task.FromResult(DateTimeOffset.MinValue); + ? Task.FromResult(DateTimeOffset.MaxValue) + : Task.FromResult(DateTimeOffset.MinValue); } /// /// Locks a user out until the specified end date (set to a past date, to unlock a user) /// /// + /// /// /// /// Currently we do not support a timed lock out, when they are locked out, an admin will have to reset the status /// - public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd) + public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); - user.LockoutEndDateUtc = lockoutEnd.UtcDateTime; - return Task.FromResult(0); + + user.LockoutEndDateUtc = lockoutEnd.Value.UtcDateTime; + return Task.CompletedTask; } /// /// Used to record when an attempt to access the user has failed /// /// + /// /// - public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user) + public Task IncrementAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); + user.AccessFailedCount++; return Task.FromResult(user.AccessFailedCount); } @@ -574,12 +695,16 @@ namespace Umbraco.Core.Security /// Used to reset the access failed count, typically after the account is successfully accessed /// /// + /// /// - public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user) + public Task ResetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); + user.AccessFailedCount = 0; - return Task.FromResult(0); + return Task.CompletedTask; } /// @@ -587,9 +712,12 @@ namespace Umbraco.Core.Security /// verified or the account is locked out. /// /// + /// /// - public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user) + public Task GetAccessFailedCountAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.AccessFailedCount); } @@ -598,9 +726,12 @@ namespace Umbraco.Core.Security /// Returns true /// /// + /// /// - public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user) + public Task GetLockoutEnabledAsync(BackOfficeIdentityUser user, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); return Task.FromResult(user.LockoutEnabled); } @@ -609,12 +740,16 @@ namespace Umbraco.Core.Security /// Doesn't actually perform any function, users can always be locked out /// /// + /// /// - public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled) + public Task SetLockoutEnabledAsync(BackOfficeIdentityUser user, bool enabled, CancellationToken cancellationToken = default(CancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfDisposed(); if (user == null) throw new ArgumentNullException(nameof(user)); + user.LockoutEnabled = enabled; - return Task.FromResult(0); + return Task.CompletedTask; } #endregion @@ -756,21 +891,28 @@ namespace Umbraco.Core.Security return anythingChanged; } - private void ThrowIfDisposed() { - if (_disposed) - throw new ObjectDisposedException(GetType().Name); + if (_disposed) throw new ObjectDisposedException(GetType().Name); } - public Task ValidateSessionIdAsync(int userId, string sessionId) + public Task ValidateSessionIdAsync(string userId, string sessionId) { Guid guidSessionId; if (Guid.TryParse(sessionId, out guidSessionId)) { - return Task.FromResult(_userService.ValidateLoginSession(userId, guidSessionId)); + return Task.FromResult(_userService.ValidateLoginSession(UserIdToInt(userId), guidSessionId)); } + return Task.FromResult(false); } + + private static int UserIdToInt(string userId) + { + var attempt = userId.TryConvertTo(); + if (attempt.Success) return attempt.Result; + + throw new InvalidOperationException("Unable to convert user ID to int", attempt.Exception); + } } } diff --git a/src/Umbraco.Web/Security/BackOfficeUserValidator.cs b/src/Umbraco.Web/Security/BackOfficeUserValidator.cs index 0f6b9aa1d4..94e9c2e0bd 100644 --- a/src/Umbraco.Web/Security/BackOfficeUserValidator.cs +++ b/src/Umbraco.Web/Security/BackOfficeUserValidator.cs @@ -1,26 +1,18 @@ using System.Threading.Tasks; -using Microsoft.AspNet.Identity; +using Microsoft.AspNetCore.Identity; using Umbraco.Web.Models.Identity; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { - /// - /// Custom validator to not validate a user's username or email if they haven't changed - /// - /// - internal class BackOfficeUserValidator : UserValidator + public class BackOfficeUserValidator : UserValidator where T : BackOfficeIdentityUser { - public BackOfficeUserValidator(UserManager manager) : base(manager) + public override async Task ValidateAsync(UserManager manager, T user) { - } - - public override async Task ValidateAsync(T item) - { - //Don't validate if the user's email or username hasn't changed otherwise it's just wasting SQL queries. - if (item.IsPropertyDirty("Email") || item.IsPropertyDirty("UserName")) + // Don't validate if the user's email or username hasn't changed otherwise it's just wasting SQL queries. + if (user.IsPropertyDirty("Email") || user.IsPropertyDirty("UserName")) { - return await base.ValidateAsync(item); + return await base.ValidateAsync(manager, user); } return IdentityResult.Success; } diff --git a/src/Umbraco.Web/Security/EmailService.cs b/src/Umbraco.Web/Security/EmailService.cs deleted file mode 100644 index aabd5a1ead..0000000000 --- a/src/Umbraco.Web/Security/EmailService.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Net.Mail; -using System.Threading.Tasks; -using Microsoft.AspNet.Identity; - -namespace Umbraco.Core.Security -{ - /// - /// The implementation for Umbraco - /// - public class EmailService : IIdentityMessageService - { - private readonly string _notificationEmailAddress; - private readonly IEmailSender _defaultEmailSender; - - public EmailService(string notificationEmailAddress, IEmailSender defaultEmailSender) - { - _notificationEmailAddress = notificationEmailAddress; - _defaultEmailSender = defaultEmailSender; - } - - - public async Task SendAsync(IdentityMessage message) - { - var mailMessage = new MailMessage( - _notificationEmailAddress, - message.Destination, - message.Subject, - message.Body) - { - IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false - && message.Body.Contains("<") && message.Body.Contains(" - /// A password hasher that is User aware so that it can process the hashing based on the user's settings - /// - /// - /// - public interface IUserAwarePasswordHasher : Microsoft.AspNet.Identity.IPasswordHasher - where TUser : class, IUser - where TKey : IEquatable - { - string HashPassword(TUser user, string password); - PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword); - } -} diff --git a/src/Umbraco.Web/Security/IUserSessionStore.cs b/src/Umbraco.Web/Security/IUserSessionStore.cs index 3454b19f84..06b7c2f165 100644 --- a/src/Umbraco.Web/Security/IUserSessionStore.cs +++ b/src/Umbraco.Web/Security/IUserSessionStore.cs @@ -1,6 +1,5 @@ -using System; using System.Threading.Tasks; -using Microsoft.AspNet.Identity; +using Microsoft.AspNetCore.Identity; namespace Umbraco.Core.Security { @@ -8,10 +7,9 @@ namespace Umbraco.Core.Security /// An IUserStore interface part to implement if the store supports validating user session Ids /// /// - /// - public interface IUserSessionStore : IUserStore, IDisposable - where TUser : class, IUser + public interface IUserSessionStore : IUserStore + where TUser : class { - Task ValidateSessionIdAsync(int userId, string sessionId); + Task ValidateSessionIdAsync(string userId, string sessionId); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs new file mode 100644 index 0000000000..2975149107 --- /dev/null +++ b/src/Umbraco.Web/Security/IdentityFactoryMiddleware.cs @@ -0,0 +1,106 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Owin; +using Microsoft.Owin.Security.DataProtection; + +namespace Umbraco.Web.Security +{ + /// + /// Adapted from Microsoft.AspNet.Identity.Owin.IdentityFactoryMiddleware + /// + public class IdentityFactoryMiddleware : OwinMiddleware + where TResult : class, IDisposable + where TOptions : IdentityFactoryOptions + { + /// The next middleware in the OWIN pipeline to invoke + /// Configuration options for the middleware + public IdentityFactoryMiddleware(OwinMiddleware next, TOptions options) + : base(next) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (options.Provider == null) throw new ArgumentException("options.Provider"); + + Options = options; + } + + /// + /// Configuration options + /// + public TOptions Options { get; private set; } + + /// + /// Create an object using the Options.Provider, storing it in the OwinContext and then disposes the object when finished + /// + /// + /// + public override async Task Invoke(IOwinContext context) + { + var instance = Options.Provider.Create(Options, context); + try + { + context.Set(instance); + if (Next != null) + { + await Next.Invoke(context); + } + } + finally + { + Options.Provider.Dispose(Options, instance); + } + } + } + + public class IdentityFactoryOptions where T : class, IDisposable + { + /// + /// Used to configure the data protection provider + /// + public IDataProtectionProvider DataProtectionProvider { get; set; } + + /// + /// Provider used to Create and Dispose objects + /// + public IdentityFactoryProvider Provider { get; set; } + } + + public class IdentityFactoryProvider where T : class, IDisposable + { + public IdentityFactoryProvider() + { + OnDispose = (options, instance) => { }; + OnCreate = (options, context) => null; + } + + /// + /// A delegate assigned to this property will be invoked when the related method is called + /// + public Func, IOwinContext, T> OnCreate { get; set; } + + /// + /// A delegate assigned to this property will be invoked when the related method is called + /// + public Action, T> OnDispose { get; set; } + + /// + /// Calls the OnCreate Delegate + /// + /// + /// + /// + public virtual T Create(IdentityFactoryOptions options, IOwinContext context) + { + return OnCreate(options, context); + } + + /// + /// Calls the OnDispose delegate + /// + /// + /// + public virtual void Dispose(IdentityFactoryOptions options, T instance) + { + OnDispose(options, instance); + } + } +} diff --git a/src/Umbraco.Web/Security/NopLookupNormalizer.cs b/src/Umbraco.Web/Security/NopLookupNormalizer.cs new file mode 100644 index 0000000000..08aa8d548a --- /dev/null +++ b/src/Umbraco.Web/Security/NopLookupNormalizer.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Identity; + +namespace Umbraco.Web.Security +{ + /// + /// No-op lookup normalizer to maintain compatibility with ASP.NET Identity 2 + /// + public class NopLookupNormalizer : ILookupNormalizer + { + public string NormalizeName(string name) => name; + + public string NormalizeEmail(string email) => email; + } +} diff --git a/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs b/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs new file mode 100644 index 0000000000..15bd4dfd75 --- /dev/null +++ b/src/Umbraco.Web/Security/OwinDataProtectorTokenProvider.cs @@ -0,0 +1,112 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Owin.Security.DataProtection; +using Umbraco.Web.Models.Identity; + +namespace Umbraco.Web.Security +{ + /// + /// Adapted from Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider + /// + public class OwinDataProtectorTokenProvider : IUserTwoFactorTokenProvider where TUser : BackOfficeIdentityUser + { + public TimeSpan TokenLifespan { get; set; } + private static readonly Encoding _defaultEncoding = new UTF8Encoding(false, true); + private readonly IDataProtector _protector; + + public OwinDataProtectorTokenProvider(IDataProtector protector) + { + _protector = protector ?? throw new ArgumentNullException(nameof(protector)); + TokenLifespan = TimeSpan.FromDays(1); + } + + public async Task GenerateAsync(string purpose, UserManager manager, TUser user) + { + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (user == null) throw new ArgumentNullException(nameof(user)); + + var ms = new MemoryStream(); + using (var writer = new BinaryWriter(ms, _defaultEncoding, true)) + { + writer.Write(DateTimeOffset.UtcNow.UtcTicks); + writer.Write(Convert.ToString(user.Id, CultureInfo.InvariantCulture)); + writer.Write(purpose ?? ""); + + string stamp = null; + if (manager.SupportsUserSecurityStamp) + { + stamp = await manager.GetSecurityStampAsync(user); + } + writer.Write(stamp ?? ""); + } + + var protectedBytes = _protector.Protect(ms.ToArray()); + return Convert.ToBase64String(protectedBytes); + } + + public async Task ValidateAsync(string purpose, string token, UserManager manager, TUser user) + { + if (string.IsNullOrWhiteSpace(token)) throw new ArgumentNullException(nameof(token)); + if (manager == null) throw new ArgumentNullException(nameof(manager)); + if (user == null) throw new ArgumentNullException(nameof(user)); + + try + { + var unprotectedData = _protector.Unprotect(Convert.FromBase64String(token)); + var ms = new MemoryStream(unprotectedData); + using (var reader = new BinaryReader(ms, _defaultEncoding, true)) + { + var creationTime = new DateTimeOffset(reader.ReadInt64(), TimeSpan.Zero); + var expirationTime = creationTime + TokenLifespan; + if (expirationTime < DateTimeOffset.UtcNow) + { + return false; + } + + var userId = reader.ReadString(); + if (!string.Equals(userId, Convert.ToString(user.Id, CultureInfo.InvariantCulture))) + { + return false; + } + + var purp = reader.ReadString(); + if (!string.Equals(purp, purpose)) + { + return false; + } + + var stamp = reader.ReadString(); + if (reader.PeekChar() != -1) + { + return false; + } + + if (manager.SupportsUserSecurityStamp) + { + var expectedStamp = await manager.GetSecurityStampAsync(user); + return stamp == expectedStamp; + } + + return stamp == ""; + } + } + // ReSharper disable once EmptyGeneralCatchClause + catch + { + // Do not leak exception + } + + return false; + } + + public Task CanGenerateTwoFactorTokenAsync(UserManager manager, TUser user) + { + // This token provider is designed for flows such as password reset and account confirmation + return Task.FromResult(false); + } + } +} diff --git a/src/Umbraco.Web/Security/SessionIdValidator.cs b/src/Umbraco.Web/Security/SessionIdValidator.cs index 37d86f7052..9edae8da10 100644 --- a/src/Umbraco.Web/Security/SessionIdValidator.cs +++ b/src/Umbraco.Web/Security/SessionIdValidator.cs @@ -2,8 +2,6 @@ using System; using System.Security.Claims; using System.Threading.Tasks; using System.Web; -using Microsoft.AspNet.Identity; -using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Microsoft.Owin.Security.Cookies; @@ -11,7 +9,6 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.IO; - using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Security @@ -89,11 +86,11 @@ namespace Umbraco.Web.Security if (validate == false) return true; - var manager = owinCtx.GetUserManager(); + var manager = owinCtx.Get(); if (manager == null) return false; - var userId = currentIdentity.GetUserId(); + var userId = currentIdentity.GetUserId(); var user = await manager.FindByIdAsync(userId); if (user == null) return false; diff --git a/src/Umbraco.Web/Security/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Web/Security/UmbracoBackOfficeIdentity.cs index 7817e4729f..c3697d5e9e 100644 --- a/src/Umbraco.Web/Security/UmbracoBackOfficeIdentity.cs +++ b/src/Umbraco.Web/Security/UmbracoBackOfficeIdentity.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using Microsoft.AspNet.Identity; +using Umbraco.Web; namespace Umbraco.Core.Security { @@ -117,7 +117,7 @@ namespace Umbraco.Core.Security Constants.Security.StartMediaNodeIdClaimType, ClaimTypes.Locality, Constants.Security.SessionIdClaimType, - Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType + Constants.Web.SecurityStampClaimType }; /// @@ -161,8 +161,8 @@ namespace Umbraco.Core.Security //The security stamp claim is also required... this is because this claim type is hard coded // by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444 - if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false) - AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this)); + if (HasClaim(x => x.Type == Constants.Web.SecurityStampClaimType) == false) + AddClaim(new Claim(Constants.Web.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, Issuer, Issuer, this)); //Add each app as a separate claim if (HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null) @@ -208,7 +208,7 @@ namespace Umbraco.Core.Security public string RealName => this.FindFirstValue(ClaimTypes.GivenName); - public string Username => this.GetUserName(); + public string Username => this.FindFirstValue(ClaimTypes.Name); public string Culture => this.FindFirstValue(ClaimTypes.Locality); @@ -224,7 +224,7 @@ namespace Umbraco.Core.Security } } - public string SecurityStamp => this.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType); + public string SecurityStamp => this.FindFirstValue(Constants.Web.SecurityStampClaimType); public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray(); diff --git a/src/Umbraco.Web/Security/UmbracoEmailMessage.cs b/src/Umbraco.Web/Security/UmbracoEmailMessage.cs deleted file mode 100644 index 0a81b2299b..0000000000 --- a/src/Umbraco.Web/Security/UmbracoEmailMessage.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNet.Identity; - -namespace Umbraco.Core.Security -{ - /// - /// A custom implementation for IdentityMessage that allows the customization of how an email is sent - /// - internal class UmbracoEmailMessage : IdentityMessage - { - public IEmailSender MailSender { get; private set; } - - public UmbracoEmailMessage(IEmailSender mailSender) - { - MailSender = mailSender; - } - } -} diff --git a/src/Umbraco.Web/Security/UmbracoSecurityStampValidator.cs b/src/Umbraco.Web/Security/UmbracoSecurityStampValidator.cs new file mode 100644 index 0000000000..a3f78f5262 --- /dev/null +++ b/src/Umbraco.Web/Security/UmbracoSecurityStampValidator.cs @@ -0,0 +1,88 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.Owin.Security.Cookies; +using Umbraco.Core; +using Umbraco.Web.Models.Identity; + +namespace Umbraco.Web.Security +{ + /// + /// Adapted from Microsoft.AspNet.Identity.Owin.SecurityStampValidator + /// + public class UmbracoSecurityStampValidator + { + public static Func OnValidateIdentity( + TimeSpan validateInterval, + Func> regenerateIdentityCallback, + Func getUserIdCallback) + where TSignInManager : BackOfficeSignInManager + where TManager : BackOfficeUserManager + where TUser : BackOfficeIdentityUser + { + if (getUserIdCallback == null) throw new ArgumentNullException(nameof(getUserIdCallback)); + + return async context => + { + var currentUtc = context.Options?.SystemClock?.UtcNow ?? DateTimeOffset.UtcNow; + + var issuedUtc = context.Properties.IssuedUtc; + + // Only validate if enough time has elapsed + var validate = issuedUtc == null; + if (issuedUtc != null) + { + var timeElapsed = currentUtc.Subtract(issuedUtc.Value); + validate = timeElapsed > validateInterval; + } + + if (validate) + { + var manager = context.OwinContext.Get(); + if (manager == null) throw new InvalidOperationException("Unable to load BackOfficeUserManager"); + + var signInManager = context.OwinContext.Get(); + if (signInManager == null) throw new InvalidOperationException("Unable to load BackOfficeSignInManager"); + + var userId = getUserIdCallback(context.Identity); + + if (userId != null) + { + var user = await manager.FindByIdAsync(userId); + var reject = true; + + // Refresh the identity if the stamp matches, otherwise reject + if (user != null && manager.SupportsUserSecurityStamp) + { + var securityStamp = context.Identity.FindFirstValue(Constants.Web.SecurityStampClaimType); + var newSecurityStamp = await manager.GetSecurityStampAsync(user); + + if (securityStamp == newSecurityStamp) + { + reject = false; + // Regenerate fresh claims if possible and resign in + if (regenerateIdentityCallback != null) + { + var identity = await regenerateIdentityCallback.Invoke(signInManager, manager, user); + if (identity != null) + { + // Fix for regression where this value is not updated + // Setting it to null so that it is refreshed by the cookie middleware + context.Properties.IssuedUtc = null; + context.Properties.ExpiresUtc = null; + context.OwinContext.Authentication.SignIn(context.Properties, identity); + } + } + } + } + if (reject) + { + context.RejectIdentity(); + context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType); + } + } + } + }; + } + } +} diff --git a/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs b/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs index bbfc4905bf..d804ef0ae4 100644 --- a/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs +++ b/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs @@ -1,13 +1,11 @@ -using System; -using Microsoft.AspNet.Identity; +using Microsoft.AspNetCore.Identity; +using Umbraco.Core.Security; using Umbraco.Web.Models.Identity; -namespace Umbraco.Core.Security +namespace Umbraco.Web.Security { - /// - /// The default password hasher that is User aware so that it can process the hashing based on the user's settings - /// - public class UserAwarePasswordHasher : IUserAwarePasswordHasher + public class UserAwarePasswordHasher : IPasswordHasher + where T : BackOfficeIdentityUser { private readonly PasswordSecurity _passwordSecurity; @@ -21,27 +19,22 @@ namespace Umbraco.Core.Security return _passwordSecurity.HashPasswordForStorage(password); } - public string HashPassword(BackOfficeIdentityUser user, string password) + public string HashPassword(T user, string password) { // TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089 //NOTE: For now this just falls back to the hashing we are currently using return HashPassword(password); } - - public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) - { - return _passwordSecurity.VerifyPassword(providedPassword, hashedPassword) - ? PasswordVerificationResult.Success - : PasswordVerificationResult.Failed; - } - - public PasswordVerificationResult VerifyHashedPassword(BackOfficeIdentityUser user, string hashedPassword, string providedPassword) + + public PasswordVerificationResult VerifyHashedPassword(T user, string hashedPassword, string providedPassword) { // TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089 //NOTE: For now this just falls back to the hashing we are currently using - return VerifyHashedPassword(hashedPassword, providedPassword); + return _passwordSecurity.VerifyPassword(providedPassword, hashedPassword) + ? PasswordVerificationResult.Success + : PasswordVerificationResult.Failed; } } } diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 4538666374..8975f9cde0 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -1,11 +1,9 @@ using System; -using System.Linq; using System.Security; using System.Web; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Core.Models.Membership; -using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; @@ -85,7 +83,7 @@ namespace Umbraco.Web.Security //ensure it's done for owin too owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); - var user = UserManager.FindByIdAsync(userId).Result; + var user = UserManager.FindByIdAsync(userId.ToString()).Result; SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 1b347cee36..43dedfbb22 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -337,8 +337,10 @@ namespace Umbraco.Web.Trees var nodes = GetTreeNodesInternal(id, queryStrings); - //only render the recycle bin if we are not in dialog and the start id id still the root - if (IsDialog(queryStrings) == false && id == Constants.System.RootString) + //only render the recycle bin if we are not in dialog and the start id is still the root + //we need to check for the "application" key in the queryString because its value is required here, + //and for some reason when there are no dashboards, this parameter is missing + if (IsDialog(queryStrings) == false && id == Constants.System.RootString && queryStrings.HasKey("application")) { nodes.Add(CreateTreeNode( RecycleBinId.ToInvariantString(), diff --git a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs index dc849a3154..48a0f6b84c 100644 --- a/src/Umbraco.Web/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/RelationTypeTreeController.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Actions; using Umbraco.Web.Routing; +using Umbraco.Core.Models; namespace Umbraco.Web.Trees { @@ -40,8 +41,6 @@ namespace Umbraco.Web.Trees protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) { - //TODO: Do not allow deleting built in types - var menu = _menuItemCollectionFactory.Create(); if (id == Constants.System.RootString) @@ -58,7 +57,10 @@ namespace Umbraco.Web.Trees var relationType = Services.RelationService.GetRelationTypeById(int.Parse(id)); if (relationType == null) return menu; - menu.Items.Add(Services.TextService); + if (relationType.IsSystemRelationType() == false) + { + menu.Items.Add(Services.TextService); + } return menu; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index be2c25123f..8a061987b1 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -77,11 +77,13 @@ - + + + @@ -142,8 +144,11 @@ + + + @@ -191,23 +196,28 @@ + + + + + - - + + - + @@ -218,11 +228,8 @@ - - - @@ -238,8 +245,6 @@ - - diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index 7679da2e2e..01e915b2be 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -5,6 +5,8 @@ using System.Threading; using System.Web; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Runtime; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; @@ -32,7 +34,7 @@ namespace Umbraco.Web // Determine if we should use the sql main dom or the default var appSettingMainDomLock = globalSettings.MainDomLock; var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" - ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connectionStrings, dbProviderFactoryCreator) + ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connectionStrings, dbProviderFactoryCreator, hostingEnvironment) : new MainDomSemaphoreLock(logger, hostingEnvironment); var mainDom = new MainDom(logger, mainDomLock); diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index a2b523a64e..22d7bd77d7 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -90,9 +90,8 @@ namespace Umbraco.Web // (EXPERT: an overload accepts a custom BackOfficeUserStore implementation) app.ConfigureUserManagerForUmbracoBackOffice( Services, - Mapper, - ContentSettings, GlobalSettings, + Mapper, UserPasswordConfig, IpResolver); } diff --git a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs index 5ac8d886f0..95e1a94787 100644 --- a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs @@ -9,6 +9,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// Appends a custom response header to notify the UI that the current user data has been modified /// + /// Migrated to NET core public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute { private readonly string _userIdParameter; diff --git a/src/Umbraco.Web/WebApi/Filters/HttpQueryStringModelBinder.cs b/src/Umbraco.Web/WebApi/Filters/HttpQueryStringModelBinder.cs index 4d58e2b512..3b8e4bea3f 100644 --- a/src/Umbraco.Web/WebApi/Filters/HttpQueryStringModelBinder.cs +++ b/src/Umbraco.Web/WebApi/Filters/HttpQueryStringModelBinder.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.WebApi.Filters /// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number /// but this will allow you to do it /// + /// Migrated to .NET core public sealed class HttpQueryStringModelBinder : IModelBinder { public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) diff --git a/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs index 6906519b17..710749b39f 100644 --- a/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs @@ -6,6 +6,7 @@ using System.Web.Http.Filters; namespace Umbraco.Web.WebApi.Filters { + // Migrated to .NET Core public class OnlyLocalRequestsAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) diff --git a/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs index f56d9c28c4..d4e6af93ac 100644 --- a/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/ValidationFilterAttribute.cs @@ -13,6 +13,7 @@ namespace Umbraco.Web.WebApi.Filters /// An action filter used to do basic validation against the model and return a result /// straight away if it fails. /// + /// Migrated to .NET core internal sealed class ValidationFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) diff --git a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs index 57487e29fd..2691063f19 100644 --- a/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoAuthorizedApiController.cs @@ -2,8 +2,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models.Identity; +using Umbraco.Web.WebApi.Filters;using Umbraco.Core.Models.Identity; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Models.Identity; diff --git a/src/umbraco.sln b/src/umbraco.sln index 679f3b25b9..1a9d0a4b5f 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -59,6 +59,11 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Web.UI.Client", "ht StartServerOnDebug = "false" EndProjectSection EndProject +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest\", "Umbraco.Tests.AcceptanceTest\", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}" + ProjectSection(WebsiteProperties) = preProject + SlnRelativePath = "Umbraco.Tests.AcceptanceTest\" + EndProjectSection +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web", "Umbraco.Web\Umbraco.Web.csproj", "{651E1350-91B6-44B7-BD60-7207006D7003}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests", "Umbraco.Tests\Umbraco.Tests.csproj", "{5D3B8245-ADA6-453F-A008-50ED04BFE770}" @@ -229,6 +234,7 @@ Global {FB5676ED-7A69-492C-B802-E7B24144C0FC} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {D6319409-777A-4BD0-93ED-B2DFD805B32C} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {A499779C-1B3B-48A8-B551-458E582E6E96} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} + {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC}