diff --git a/.editorconfig b/.editorconfig index 5a35b71ce6..ca85827de8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,40 +1,43 @@ -# editorconfig.org - -# top-most EditorConfig file -root = true - -# Default settings: -# A newline ending every file -# Use 4 spaces as indentation -[*] -insert_final_newline = true -end_of_line = crlf -indent_style = space -indent_size = 4 - -# Trim trailing whitespace, limited support. -# https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces -trim_trailing_whitespace = true - -[*.{cs,vb}] -dotnet_style_predefined_type_for_locals_parameters_members = true:error - -dotnet_naming_rule.private_members_with_underscore.symbols = private_fields -dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore -dotnet_naming_rule.private_members_with_underscore.severity = suggestion - -dotnet_naming_symbols.private_fields.applicable_kinds = field -dotnet_naming_symbols.private_fields.applicable_accessibilities = private - -dotnet_naming_style.prefix_underscore.capitalization = camel_case -dotnet_naming_style.prefix_underscore.required_prefix = _ - -# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md -[*.cs] -csharp_style_var_for_built_in_types = true:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion -csharp_style_var_elsewhere = true:suggestion +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +end_of_line = crlf +indent_style = space +indent_size = 4 + +# Trim trailing whitespace, limited support. +# https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces +trim_trailing_whitespace = true + +[*.{cs,vb}] +dotnet_style_predefined_type_for_locals_parameters_members = true:error + +dotnet_naming_rule.private_members_with_underscore.symbols = private_fields +dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore +dotnet_naming_rule.private_members_with_underscore.severity = suggestion + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_style.prefix_underscore.capitalization = camel_case +dotnet_naming_style.prefix_underscore.required_prefix = _ + +# https://github.com/MicrosoftDocs/visualstudio-docs/blob/master/docs/ide/editorconfig-code-style-settings-reference.md +[*.cs] +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion csharp_prefer_braces = false : none - -[*.{js,less}] + +[*.js] +trim_trailing_whitespace = true + +[*.less] trim_trailing_whitespace = false diff --git a/build/V8-Docs-Pipeline.yml b/build/V8-Docs-Pipeline.yml new file mode 100644 index 0000000000..56e8fde60c --- /dev/null +++ b/build/V8-Docs-Pipeline.yml @@ -0,0 +1,52 @@ +############################################################## +## V8 CMS - .NET & AngularJS Doc sites ## +## Built on demand only, NO automatic PR/branch triggers ## +## ## +## This build pipeline has a webhook for sucessful ## +## builds, that sends the ZIP artifacts to our.umb to host ## +############################################################## + +# Name != name of pipeline but the build number format +# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/run-number?view=azure-devops&tabs=yaml + +# Build Pipeline triggers +# https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#ci-triggers +trigger: none +pr: none + +# Variables & their default values +variables: + buildPlatform: 'Any CPU' + buildConfiguration: 'Release' + +# VM to run the build on & it's installed software +# https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops +# https://github.com/actions/virtual-environments/blob/master/images/win/Windows2019-Readme.md +pool: + vmImage: 'windows-2019' + +jobs: + - job: buildDocs + displayName: 'Build static docs site as ZIPs' + steps: + + - task: PowerShell@2 + displayName: 'Prep build tool, build C# & JS Docs' + inputs: + targetType: 'inline' + script: | + $uenv=./build.ps1 -get -doc + $uenv.SandboxNode() + $uenv.CompileBelle() + $uenv.PrepareAngularDocs() + $nugetsourceUmbraco = "https://api.nuget.org/v3/index.json" + $uenv.PrepareCSharpDocs() + $uenv.RestoreNode() + errorActionPreference: 'continue' + workingDirectory: 'build' + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: '$(Build.Repository.LocalPath)\build.out\' + artifact: 'docs' + publishLocation: 'pipeline' \ No newline at end of file diff --git a/build/build.ps1 b/build/build.ps1 index 3ba347a6dc..c2c5bdd232 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -438,14 +438,11 @@ Write-Host "Prepare C# Documentation" $src = "$($this.SolutionRoot)\src" - $tmp = $this.BuildTemp - $out = $this.BuildOutput + $tmp = $this.BuildTemp + $out = $this.BuildOutput $DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json" $DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*" - - #restore nuget packages - $this.RestoreNuGet() # run DocFx $DocFx = $this.BuildEnv.DocFx @@ -463,17 +460,22 @@ $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput + # Check if the solution has been built + if (!(Test-Path "$src\Umbraco.Web.UI.Client\node_modules")) {throw "Umbraco needs to be built before generating the Angular Docs"} + "Moving to Umbraco.Web.UI.Docs folder" - cd ..\src\Umbraco.Web.UI.Docs + cd $src\Umbraco.Web.UI.Docs "Generating the docs and waiting before executing the next commands" & npm install & npx gulp docs + Pop-Location + # change baseUrl $BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/" $IndexPath = "./api/index.html" - (Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath + (Get-Content $IndexPath).replace('origin + location.href.substr(origin.length).replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath # zip it & $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Docs\api\*.*" diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index eb2b3525a7..dcd7eb9d05 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -36,6 +36,11 @@ namespace Umbraco.Core /// public static class Aliases { + /// + /// Block List. + /// + public const string BlockList = "Umbraco.BlockList"; + /// /// CheckBox List. /// diff --git a/src/Umbraco.Core/Contants-UdiEntityType.cs b/src/Umbraco.Core/Contants-UdiEntityType.cs index 75a137bd2e..1ed862f328 100644 --- a/src/Umbraco.Core/Contants-UdiEntityType.cs +++ b/src/Umbraco.Core/Contants-UdiEntityType.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core { Unknown, UdiType.Unknown }, { AnyGuid, UdiType.GuidUdi }, + { Element, UdiType.GuidUdi }, { Document, UdiType.GuidUdi }, { DocumentBlueprint, UdiType.GuidUdi }, { Media, UdiType.GuidUdi }, @@ -64,6 +65,8 @@ namespace Umbraco.Core public const string AnyGuid = "any-guid"; // that one is for tests + public const string Element = "element"; + public const string Document = "document"; public const string DocumentBlueprint = "document-blueprint"; diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index f0cfc08281..03ba58d15e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -191,9 +191,9 @@ namespace Umbraco.Core.Migrations.Upgrade To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}"); To("{2AB29964-02A1-474D-BD6B-72148D2A53A2}"); - + // to 8.7.0... To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); - + //FINAL } } diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs new file mode 100644 index 0000000000..d8186f0dfa --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorData.cs @@ -0,0 +1,45 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + + /// + /// Convertable block data from json + /// + public class BlockEditorData + { + private readonly string _propertyEditorAlias; + + public static BlockEditorData Empty { get; } = new BlockEditorData(); + + private BlockEditorData() + { + BlockValue = new BlockValue(); + } + + public BlockEditorData(string propertyEditorAlias, + IEnumerable references, + BlockValue blockValue) + { + if (string.IsNullOrWhiteSpace(propertyEditorAlias)) + throw new ArgumentException($"'{nameof(propertyEditorAlias)}' cannot be null or whitespace", nameof(propertyEditorAlias)); + _propertyEditorAlias = propertyEditorAlias; + BlockValue = blockValue ?? throw new ArgumentNullException(nameof(blockValue)); + References = references != null ? new List(references) : throw new ArgumentNullException(nameof(references)); + } + + /// + /// Returns the layout for this specific property editor + /// + public JToken Layout => BlockValue.Layout.TryGetValue(_propertyEditorAlias, out var layout) ? layout : null; + + /// + /// Returns the reference to the original BlockValue + /// + public BlockValue BlockValue { get; } + + public List References { get; } = new List(); + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs new file mode 100644 index 0000000000..22e364c0f8 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + + /// + /// Converts the block json data into objects + /// + public abstract class BlockEditorDataConverter + { + private readonly string _propertyEditorAlias; + + protected BlockEditorDataConverter(string propertyEditorAlias) + { + _propertyEditorAlias = propertyEditorAlias; + } + + public BlockEditorData Deserialize(string json) + { + var value = JsonConvert.DeserializeObject(json); + + if (value.Layout == null) + return BlockEditorData.Empty; + + var references = value.Layout.TryGetValue(_propertyEditorAlias, out var layout) + ? GetBlockReferences(layout) + : Enumerable.Empty(); + + return new BlockEditorData(_propertyEditorAlias, references, value); + } + + /// + /// Return the collection of from the block editor's Layout (which could be an array or an object depending on the editor) + /// + /// + /// + protected abstract IEnumerable GetBlockReferences(JToken jsonLayout); + + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorModel.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorModel.cs new file mode 100644 index 0000000000..fa5a29fece --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorModel.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// The base class for any strongly typed model for a Block editor implementation + /// + public abstract class BlockEditorModel + { + protected BlockEditorModel(IEnumerable contentData, IEnumerable settingsData) + { + ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData)); + SettingsData = settingsData ?? new List(); + } + + public BlockEditorModel() + { + } + + + /// + /// The content data items of the Block List editor + /// + [DataMember(Name = "contentData")] + public IEnumerable ContentData { get; set; } = new List(); + + /// + /// The settings data items of the Block List editor + /// + [DataMember(Name = "settingsData")] + public IEnumerable SettingsData { get; set; } = new List(); + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockItemData.cs b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs new file mode 100644 index 0000000000..12a636771e --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockItemData.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Represents a single block's data in raw form + /// + public class BlockItemData + { + [JsonProperty("contentTypeKey")] + public Guid ContentTypeKey { get; set; } + + /// + /// not serialized, manually set and used during internally + /// + [JsonIgnore] + public string ContentTypeAlias { get; set; } + + [JsonProperty("udi")] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi Udi { get; set; } + + [JsonIgnore] + public Guid Key => Udi != null ? ((GuidUdi)Udi).Guid : throw new InvalidOperationException("No Udi assigned"); + + /// + /// The remaining properties will be serialized to a dictionary + /// + /// + /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket + /// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm + /// NestedContent serializes to string, int, whatever eg + /// "stringValue":"Some String","numericValue":125,"otherNumeric":null + /// + [JsonExtensionData] + public Dictionary RawPropertyValues { get; set; } = new Dictionary(); + + /// + /// Used during deserialization to convert the raw property data into data with a property type context + /// + [JsonIgnore] + public IDictionary PropertyValues { get; set; } = new Dictionary(); + + /// + /// Used during deserialization to populate the property value/property type of a block item content property + /// + public class BlockPropertyValue + { + public object Value { get; set; } + public PropertyType PropertyType { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs new file mode 100644 index 0000000000..23f69922d9 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListEditorDataConverter.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json.Linq; +using System.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Data converter for the block list property editor + /// + public class BlockListEditorDataConverter : BlockEditorDataConverter + { + public BlockListEditorDataConverter() : base(Constants.PropertyEditors.Aliases.BlockList) + { + } + + protected override IEnumerable GetBlockReferences(JToken jsonLayout) + { + var blockListLayout = jsonLayout.ToObject>(); + return blockListLayout.Select(x => new ContentAndSettingsReference(x.ContentUdi, x.SettingsUdi)).ToList(); + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs new file mode 100644 index 0000000000..3453ff2a78 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using Umbraco.Core.Serialization; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Used for deserializing the block list layout + /// + public class BlockListLayoutItem + { + [JsonProperty("contentUdi", Required = Required.Always)] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi ContentUdi { get; set; } + + [JsonProperty("settingsUdi")] + [JsonConverter(typeof(UdiJsonConverter))] + public Udi SettingsUdi { get; set; } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListLayoutReference.cs b/src/Umbraco.Core/Models/Blocks/BlockListLayoutReference.cs new file mode 100644 index 0000000000..f576bd927f --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListLayoutReference.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.Serialization; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// Represents a layout item for the Block List editor + /// + [DataContract(Name = "blockListLayout", Namespace = "")] + public class BlockListLayoutReference : IBlockReference + { + public BlockListLayoutReference(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings) + { + ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); + Content = content ?? throw new ArgumentNullException(nameof(content)); + Settings = settings; // can be null + SettingsUdi = settingsUdi; // can be null + } + + /// + /// The Id of the content data item + /// + [DataMember(Name = "contentUdi")] + public Udi ContentUdi { get; } + + /// + /// The Id of the settings data item + /// + [DataMember(Name = "settingsUdi")] + public Udi SettingsUdi { get; } + + /// + /// The content data item referenced + /// + /// + /// This is ignored from serialization since it is just a reference to the actual data element + /// + [IgnoreDataMember] + public IPublishedElement Content { get; } + + /// + /// The settings data item referenced + /// + /// + /// This is ignored from serialization since it is just a reference to the actual data element + /// + [IgnoreDataMember] + public IPublishedElement Settings { get; } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockListModel.cs b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs new file mode 100644 index 0000000000..0492cf0d73 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.Models.Blocks +{ + /// + /// The strongly typed model for the Block List editor + /// + [DataContract(Name = "blockList", Namespace = "")] + public class BlockListModel : BlockEditorModel + { + public static BlockListModel Empty { get; } = new BlockListModel(); + + private BlockListModel() + { + } + + public BlockListModel(IEnumerable contentData, IEnumerable settingsData, IEnumerable layout) + : base(contentData, settingsData) + { + Layout = layout; + } + + /// + /// The layout items of the Block List editor + /// + [DataMember(Name = "layout")] + public IEnumerable Layout { get; } = new List(); + + + } +} diff --git a/src/Umbraco.Core/Models/Blocks/BlockValue.cs b/src/Umbraco.Core/Models/Blocks/BlockValue.cs new file mode 100644 index 0000000000..4700ddfd3b --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/BlockValue.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.Blocks +{ + public class BlockValue + { + [JsonProperty("layout")] + public IDictionary Layout { get; set; } + + [JsonProperty("contentData")] + public List ContentData { get; set; } = new List(); + + [JsonProperty("settingsData")] + public List SettingsData { get; set; } = new List(); + } +} diff --git a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs new file mode 100644 index 0000000000..523a964c7b --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System; + +namespace Umbraco.Core.Models.Blocks +{ + public struct ContentAndSettingsReference : IEquatable + { + public ContentAndSettingsReference(Udi contentUdi, Udi settingsUdi) + { + ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); + SettingsUdi = settingsUdi; + } + + public Udi ContentUdi { get; } + public Udi SettingsUdi { get; } + + public override bool Equals(object obj) + { + return obj is ContentAndSettingsReference reference && Equals(reference); + } + + public bool Equals(ContentAndSettingsReference other) + { + return EqualityComparer.Default.Equals(ContentUdi, other.ContentUdi) && + EqualityComparer.Default.Equals(SettingsUdi, other.SettingsUdi); + } + + public override int GetHashCode() + { + var hashCode = 272556606; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContentUdi); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(SettingsUdi); + return hashCode; + } + + public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) + { + return left.Equals(right); + } + + public static bool operator !=(ContentAndSettingsReference left, ContentAndSettingsReference right) + { + return !(left == right); + } + } +} diff --git a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs new file mode 100644 index 0000000000..8d8ddd47f0 --- /dev/null +++ b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs @@ -0,0 +1,26 @@ +namespace Umbraco.Core.Models.Blocks +{ + + /// + /// Represents a data item reference for a Block editor implementation + /// + /// + /// + /// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed + /// + public interface IBlockReference : IBlockReference + { + TSettings Settings { get; } + } + + /// + /// Represents a data item reference for a Block Editor implementation + /// + /// + /// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed + /// + public interface IBlockReference + { + Udi ContentUdi { get; } + } +} diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 6990a7f7da..7a4fc83253 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content is published. /// + /// The property tells you which version of the content is currently published. bool Published { get; set; } PublishedState PublishedState { get; set; } @@ -32,10 +33,11 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content has been edited. /// + /// Will return `true` once unpublished edits have been made after the version with has been published. bool Edited { get; set; } /// - /// Gets the published version identifier. + /// Gets the version identifier for the currently published version of the content. /// int PublishedVersionId { get; set; } @@ -129,6 +131,6 @@ namespace Umbraco.Core.Models /// /// IContent DeepCloneWithResetIdentities(); - + } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs index ab6920377c..cfc789324a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -1,7 +1,21 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent { + /// + /// Represents an type. + /// + /// Instances implementing the interface should be + /// immutable, ie if the content type changes, then a new instance needs to be created. + public interface IPublishedContentType2 : IPublishedContentType + { + /// + /// Gets the unique key for the content type. + /// + Guid Key { get; } + } + /// /// Represents an type. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index 89009ac7b8..1a29f970d9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -1,5 +1,6 @@ namespace Umbraco. Core.Models.PublishedContent { + /// /// Creates published content types. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs index ae4caf352e..60fa0fe603 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs @@ -3,6 +3,7 @@ using System.Collections; namespace Umbraco.Core.Models.PublishedContent { + /// /// Provides the published model creation service. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index bfc65b70d6..033396e4a1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -3,6 +3,7 @@ using Umbraco.Core.Composing; namespace Umbraco.Core.Models.PublishedContent { + /// /// Provides strongly typed published content models services. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 458b63ade3..7aa9b0dfd9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Instances of the class are immutable, ie /// if the content type changes, then a new class needs to be created. - public class PublishedContentType : IPublishedContentType + public class PublishedContentType : IPublishedContentType2 { private readonly IPublishedPropertyType[] _propertyTypes; @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.PublishedContent /// Initializes a new instance of the class with a content type. /// public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) - : this(contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) + : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) { var propertyTypes = contentType.CompositionPropertyTypes .Select(x => factory.CreatePropertyType(this, x)) @@ -40,8 +40,20 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Values are assumed to be consistent and are not checked. /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + var propertyTypesA = propertyTypes.ToArray(); + foreach (var propertyType in propertyTypesA) + propertyType.ContentType = this; + _propertyTypes = propertyTypesA; + + InitializeIndexes(); + } + + [Obsolete("Use the overload specifying a key instead")] public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) - : this (id, alias, itemType, compositionAliases, variations, isElement) + : this (Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) { var propertyTypesA = propertyTypes.ToArray(); foreach (var propertyType in propertyTypesA) @@ -57,16 +69,26 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Values are assumed to be consistent and are not checked. /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); + + InitializeIndexes(); + } + + [Obsolete("Use the overload specifying a key instead")] public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) - : this(id, alias, itemType, compositionAliases, variations, isElement) + : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) { _propertyTypes = propertyTypes(this).ToArray(); InitializeIndexes(); } - private PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) + private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) { + Key = key; Id = id; Alias = alias; ItemType = itemType; @@ -116,6 +138,9 @@ namespace Umbraco.Core.Models.PublishedContent #region Content type + /// + public Guid Key { get; } + /// public int Id { get; } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs new file mode 100644 index 0000000000..feab33c1d6 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + public static class PublishedContentTypeExtensions + { + /// + /// Get the GUID key from an + /// + /// + /// + /// + public static bool TryGetKey(this IPublishedContentType publishedContentType, out Guid key) + { + if (publishedContentType is IPublishedContentType2 contentTypeWithKey) + { + key = contentTypeWithKey.Key; + return true; + } + key = Guid.Empty; + return false; + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 34094508c3..c1548d3c3d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -35,18 +35,18 @@ namespace Umbraco.Core.Models.PublishedContent /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + internal IPublishedContentType CreateContentType(Guid key, int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { - return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); + return new PublishedContentType(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); } /// /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) + internal IPublishedContentType CreateContentType(Guid key, int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) { - return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); + return new PublishedContentType(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); } /// diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 5c81017ec8..45b9d0ecfc 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -37,6 +37,7 @@ namespace Umbraco.Core.Models if (contentType == null) throw new ArgumentNullException(nameof(contentType)); Id = contentType.Id; + Key = contentType.Key; Alias = contentType.Alias; Variations = contentType.Variations; Icon = contentType.Icon; @@ -51,6 +52,8 @@ namespace Umbraco.Core.Models public int Id { get; } + public Guid Key { get; } + /// public ITemplate DefaultTemplate { get; } @@ -109,13 +112,14 @@ namespace Umbraco.Core.Models string ITreeEntity.Name { get => this.Name; set => throw new NotImplementedException(); } int IEntity.Id { get => this.Id; set => throw new NotImplementedException(); } bool IEntity.HasIdentity => this.Id != default; + Guid IEntity.Key { get => this.Key; set => throw new NotImplementedException(); } + int ITreeEntity.CreatorId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } int ITreeEntity.ParentId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } int ITreeEntity.Level { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } string ITreeEntity.Path { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } int ITreeEntity.SortOrder { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - bool ITreeEntity.Trashed => throw new NotImplementedException(); - Guid IEntity.Key { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + bool ITreeEntity.Trashed => throw new NotImplementedException(); DateTime IEntity.CreateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } DateTime IEntity.UpdateDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } DateTime? IEntity.DeleteDate { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs index 849fd35fad..f763594616 100644 --- a/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs +++ b/src/Umbraco.Core/Persistence/FaultHandling/Strategies/SqlAzureTransientErrorDetectionStrategy.cs @@ -4,6 +4,10 @@ using System.Data.SqlClient; namespace Umbraco.Core.Persistence.FaultHandling.Strategies { + // See https://docs.microsoft.com/en-us/azure/azure-sql/database/troubleshoot-common-connectivity-issues + // Also we could just use the nuget package instead https://www.nuget.org/packages/EnterpriseLibrary.TransientFaultHandling/ ? + // but i guess that's not netcore so we'll just leave it. + /// /// Provides the transient error detection logic for transient faults that are specific to SQL Azure. /// @@ -71,7 +75,7 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies /// Determines whether the specified exception represents a transient failure that can be compensated by a retry. /// /// The exception object to be verified. - /// True if the specified exception is considered as transient, otherwise false. + /// true if the specified exception is considered as transient; otherwise, false. public bool IsTransient(Exception ex) { if (ex != null) @@ -97,40 +101,50 @@ namespace Umbraco.Core.Persistence.FaultHandling.Strategies return true; - // SQL Error Code: 40197 - // The service has encountered an error processing your request. Please try again. - case 40197: + // SQL Error Code: 10928 + // Resource ID: %d. The %s limit for the database is %d and has been reached. + case 10928: + // SQL Error Code: 10929 + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // However, the server is currently too busy to support requests greater than %d for this database. + case 10929: // SQL Error Code: 10053 // A transport-level error has occurred when receiving results from the server. // An established connection was aborted by the software in your host machine. case 10053: // SQL Error Code: 10054 - // A transport-level error has occurred when sending the request to the server. + // A transport-level error has occurred when sending the request to the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 10054: // SQL Error Code: 10060 - // A network-related or instance-specific error occurred while establishing a connection to SQL Server. - // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server - // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed - // because the connected party did not properly respond after a period of time, or established connection failed + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed // because connected host has failed to respond.)"} case 10060: + // SQL Error Code: 40197 + // The service has encountered an error processing your request. Please try again. + case 40197: + // SQL Error Code: 40540 + // The service has encountered an error processing your request. Please try again. + case 40540: // SQL Error Code: 40613 - // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer + // Database XXXX on server YYYY is not currently available. Please retry the connection later. If the problem persists, contact customer // support, and provide them the session tracing ID of ZZZZZ. case 40613: // SQL Error Code: 40143 // The service has encountered an error processing your request. Please try again. case 40143: // SQL Error Code: 233 - // The client was unable to establish a connection because of an error during connection initialization process before login. - // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy - // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy + // to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server. // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) case 233: // SQL Error Code: 64 - // A connection was successfully established with the server, but then an error occurred during the login process. - // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) case 64: // DBNETLIB Error Code: 20 // The instance of SQL Server you attempted to connect to does not support encryption. diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index fc5382499f..0971b2047a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -12,6 +12,11 @@ namespace Umbraco.Core.Persistence.Repositories /// void ClearSchedule(DateTime date); + void ClearSchedule(DateTime date, ContentScheduleAction action); + + bool HasContentForExpiration(DateTime date); + bool HasContentForRelease(DateTime date); + /// /// Gets objects having an expiration date before (lower than, or equal to) a specified date. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index db1e2b350d..e4ef939870 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1017,6 +1017,37 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(sql); } + /// + public void ClearSchedule(DateTime date, ContentScheduleAction action) + { + var a = action.ToString(); + var sql = Sql().Delete().Where(x => x.Date <= date && x.Action == a); + Database.Execute(sql); + } + + private Sql GetSqlForHasScheduling(ContentScheduleAction action, DateTime date) + { + var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetSqlForHasScheduling", tsql => tsql + .SelectCount() + .From() + .Where(x => x.Action == SqlTemplate.Arg("action") && x.Date <= SqlTemplate.Arg("date"))); + + var sql = template.Sql(action.ToString(), date); + return sql; + } + + public bool HasContentForExpiration(DateTime date) + { + var sql = GetSqlForHasScheduling(ContentScheduleAction.Expire, date); + return Database.ExecuteScalar(sql) > 0; + } + + public bool HasContentForRelease(DateTime date) + { + var sql = GetSqlForHasScheduling(ContentScheduleAction.Release, date); + return Database.ExecuteScalar(sql) > 0; + } + /// public IEnumerable GetContentForRelease(DateTime date) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs index ab217d3870..a5e69d9650 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValue.cs @@ -157,12 +157,11 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters // merge the crop values - the alias + width + height comes from // configuration, but each crop can store its own coordinates - if (Crops == null) return; - var configuredCrops = configuration?.Crops; if (configuredCrops == null) return; - var crops = Crops.ToList(); + //Use Crops if it's not null, otherwise create a new list + var crops = Crops?.ToList() ?? new List(); foreach (var configuredCrop in configuredCrops) { diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 5f5d0d607f..04abc83ba2 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -21,12 +21,11 @@ namespace Umbraco.Core.Runtime private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom"; private const string UpdatedSuffix = "_updated"; private readonly ILogger _logger; - private IUmbracoDatabase _db; private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider(); private bool _mainDomChanging = false; private readonly UmbracoDatabaseFactory _dbFactory; - private bool _hasError; + private bool _errorDuringAcquiring; private object _locker = new object(); public SqlMainDomLock(ILogger logger) @@ -56,25 +55,24 @@ namespace Umbraco.Core.Runtime _logger.Debug("Acquiring lock..."); - var db = GetDatabase(); - var tempId = Guid.NewGuid().ToString(); + using var db = _dbFactory.CreateDatabase(); + using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted); + try { - db.BeginTransaction(IsolationLevel.ReadCommitted); - try { // wait to get a write lock _sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom); } - catch (Exception ex) + catch(SqlException ex) { if (IsLockTimeoutException(ex)) { _logger.Error(ex, "Sql timeout occurred, could not acquire MainDom."); - _hasError = true; + _errorDuringAcquiring = true; return false; } @@ -82,15 +80,12 @@ namespace Umbraco.Core.Runtime throw; } - var result = InsertLockRecord(tempId); //we change the row to a random Id to signal other MainDom to shutdown + var result = InsertLockRecord(tempId, db); //we change the row to a random Id to signal other MainDom to shutdown if (result == RecordPersistenceType.Insert) { // if we've inserted, then there was no MainDom so we can instantly acquire - // TODO: see the other TODO, could we just delete the row and that would indicate that we - // are MainDom? then we don't leave any orphan rows behind. - - InsertLockRecord(_lockId); // so update with our appdomain id + InsertLockRecord(_lockId, db); // so update with our appdomain id _logger.Debug("Acquired with ID {LockId}", _lockId); return true; } @@ -100,23 +95,23 @@ namespace Umbraco.Core.Runtime } catch (Exception ex) { - ResetDatabase(); // unexpected _logger.Error(ex, "Unexpected error, cannot acquire MainDom"); - _hasError = true; + _errorDuringAcquiring = true; return false; } finally { - db?.CompleteTransaction(); + transaction.Complete(); } + return await WaitForExistingAsync(tempId, millisecondsTimeout); } public Task ListenAsync() { - if (_hasError) + if (_errorDuringAcquiring) { _logger.Warn("Could not acquire MainDom, listening is canceled."); return Task.CompletedTask; @@ -142,8 +137,15 @@ namespace Umbraco.Core.Runtime { while (true) { - // poll every 1 second - Thread.Sleep(1000); + // poll every couple of seconds + // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO + Thread.Sleep(2000); + + if (!_dbFactory.Configured) + { + // if we aren't configured, we just keep looping since we can't query the db + continue; + } if (!_dbFactory.Configured) { @@ -160,20 +162,14 @@ namespace Umbraco.Core.Runtime if (_cancellationTokenSource.IsCancellationRequested) return; - var db = GetDatabase(); - + using var db = _dbFactory.CreateDatabase(); + using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted); try { - db.BeginTransaction(IsolationLevel.ReadCommitted); - // get a read lock _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - // TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that - // we are still the maindom. An empty value might be better because then we won't have any orphan rows - // if the app is terminated. Could that work? - - if (!IsMainDomValue(_lockId)) + if (!IsMainDomValue(_lockId, db)) { // we are no longer main dom, another one has come online, exit _mainDomChanging = true; @@ -183,38 +179,23 @@ namespace Umbraco.Core.Runtime } catch (Exception ex) { - ResetDatabase(); - // unexpected - _logger.Error(ex, "Unexpected error, listening is canceled."); - _hasError = true; - return; + _logger.Error(ex, "Unexpected error during listening."); + + // We need to keep on listening unless we've been notified by our own AppDomain to shutdown since + // we don't want to shutdown resources controlled by MainDom inadvertently. We'll just keep listening otherwise. + if (_cancellationTokenSource.IsCancellationRequested) + return; + } finally { - db?.CompleteTransaction(); + transaction.Complete(); } } } } - private void ResetDatabase() - { - if (_db.InTransaction) - _db.AbortTransaction(); - _db.Dispose(); - _db = null; - } - - private IUmbracoDatabase GetDatabase() - { - if (_db != null) - return _db; - - _db = _dbFactory.CreateDatabase(); - return _db; - } - /// /// Wait for any existing MainDom to release so we can continue booting /// @@ -227,121 +208,131 @@ namespace Umbraco.Core.Runtime return Task.Run(() => { - var db = GetDatabase(); + using var db = _dbFactory.CreateDatabase(); + var watch = new Stopwatch(); watch.Start(); - while(true) + while (true) { // poll very often, we need to take over as fast as we can - Thread.Sleep(100); + // local testing shows the actual query to be executed from client/server is approx 300ms but would change depending on environment/IO + Thread.Sleep(1000); - try - { - db.BeginTransaction(IsolationLevel.ReadCommitted); - - // get a read lock - _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); - - // the row - var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); - - if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) - { - // the other main dom has updated our record - // Or the other maindom shutdown super fast and just deleted the record - // which indicates that we - // can acquire it and it has shutdown. - - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); - - // so now we update the row with our appdomain id - InsertLockRecord(_lockId); - _logger.Debug("Acquired with ID {LockId}", _lockId); - return true; - } - else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) - { - // in this case, the prefixed ID is different which means - // another new AppDomain has come online and is wanting to take over. In that case, we will not - // acquire. - - _logger.Debug("Cannot acquire, another booting application detected."); - - return false; - } - } - catch (Exception ex) - { - ResetDatabase(); - - if (IsLockTimeoutException(ex)) - { - _logger.Error(ex, "Sql timeout occurred, waiting for existing MainDom is canceled."); - _hasError = true; - return false; - } - // unexpected - _logger.Error(ex, "Unexpected error, waiting for existing MainDom is canceled."); - _hasError = true; - return false; - } - finally - { - db?.CompleteTransaction(); - } + var acquired = TryAcquire(db, tempId, updatedTempId); + if (acquired.HasValue) + return acquired.Value; if (watch.ElapsedMilliseconds >= millisecondsTimeout) { - // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown, - // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row - // and it's just been left as an orphan row. - // There's really know way of knowing unless we are constantly updating the row for the current maindom - // which isn't ideal. - // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok - - _logger.Debug("Timeout elapsed, assuming orphan row, acquiring MainDom."); - - try - { - db.BeginTransaction(IsolationLevel.ReadCommitted); - - _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); - - // so now we update the row with our appdomain id - InsertLockRecord(_lockId); - _logger.Debug("Acquired with ID {LockId}", _lockId); - return true; - } - catch (Exception ex) - { - ResetDatabase(); - - if (IsLockTimeoutException(ex)) - { - // something is wrong, we cannot acquire, not much we can do - _logger.Error(ex, "Sql timeout occurred, could not forcibly acquire MainDom."); - _hasError = true; - return false; - } - _logger.Error(ex, "Unexpected error, could not forcibly acquire MainDom."); - _hasError = true; - return false; - } - finally - { - db?.CompleteTransaction(); - } + return AcquireWhenMaxWaitTimeElapsed(db); } } }, _cancellationTokenSource.Token); } + private bool? TryAcquire(IUmbracoDatabase db, string tempId, string updatedTempId) + { + using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted); + + try + { + // get a read lock + _sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom); + + // the row + var mainDomRows = db.Fetch("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + + if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId) + { + // the other main dom has updated our record + // Or the other maindom shutdown super fast and just deleted the record + // which indicates that we + // can acquire it and it has shutdown. + + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId, db); + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId)) + { + // in this case, the prefixed ID is different which means + // another new AppDomain has come online and is wanting to take over. In that case, we will not + // acquire. + + _logger.Debug("Cannot acquire, another booting application detected."); + return false; + } + } + catch (Exception ex) + { + if (IsLockTimeoutException(ex as SqlException)) + { + _logger.Error(ex, "Sql timeout occurred, waiting for existing MainDom is canceled."); + _errorDuringAcquiring = true; + return false; + } + // unexpected + _logger.Error(ex, "Unexpected error, waiting for existing MainDom is canceled."); + _errorDuringAcquiring = true; + return false; + } + finally + { + transaction.Complete(); + } + + return null; // continue + } + + private bool AcquireWhenMaxWaitTimeElapsed(IUmbracoDatabase db) + { + // if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown, + // or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row + // and it's just been left as an orphan row. + // There's really know way of knowing unless we are constantly updating the row for the current maindom + // which isn't ideal. + // So... we're going to 'just' take over, if the writelock works then we'll assume we're ok + + _logger.Debug("Timeout elapsed, assuming orphan row, acquiring MainDom."); + + using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted); + + try + { + _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); + + // so now we update the row with our appdomain id + InsertLockRecord(_lockId, db); + _logger.Debug("Acquired with ID {LockId}", _lockId); + return true; + } + catch (Exception ex) + { + if (IsLockTimeoutException(ex as SqlException)) + { + // something is wrong, we cannot acquire, not much we can do + _logger.Error(ex, "Sql timeout occurred, could not forcibly acquire MainDom."); + _errorDuringAcquiring = true; + return false; + } + _logger.Error(ex, "Unexpected error, could not forcibly acquire MainDom."); + _errorDuringAcquiring = true; + return false; + } + finally + { + transaction.Complete(); + } + } + /// /// Inserts or updates the key/value row /// - private RecordPersistenceType InsertLockRecord(string id) + private RecordPersistenceType InsertLockRecord(string id, IUmbracoDatabase db) { - var db = GetDatabase(); return db.InsertOrUpdate(new KeyValueDto { Key = MainDomKey, @@ -354,9 +345,8 @@ namespace Umbraco.Core.Runtime /// Checks if the DB row value is equals the value /// /// - private bool IsMainDomValue(string val) + private bool IsMainDomValue(string val, IUmbracoDatabase db) { - var db = GetDatabase(); return db.ExecuteScalar("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val", new { key = MainDomKey, val = val }) == 1; } @@ -366,7 +356,7 @@ namespace Umbraco.Core.Runtime /// /// /// - private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222; + private bool IsLockTimeoutException(SqlException sqlException) => sqlException?.Number == 1222; #region IDisposable Support private bool _disposedValue = false; // To detect redundant calls @@ -385,11 +375,11 @@ namespace Umbraco.Core.Runtime if (_dbFactory.Configured) { - var db = GetDatabase(); + using var db = _dbFactory.CreateDatabase(); + using var transaction = db.GetTransaction(IsolationLevel.ReadCommitted); + try { - db.BeginTransaction(IsolationLevel.ReadCommitted); - // get a write lock _sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom); @@ -402,24 +392,21 @@ namespace Umbraco.Core.Runtime if (_mainDomChanging) { _logger.Debug("Releasing MainDom, updating row, new application is booting."); - db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); + var count = db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey }); } else { _logger.Debug("Releasing MainDom, deleting row, application is shutting down."); - db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); + var count = db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey }); } } catch (Exception ex) { - ResetDatabase(); _logger.Error(ex, "Unexpected error during dipsose."); - _hasError = true; } finally { - db?.CompleteTransaction(); - ResetDatabase(); + transaction.Complete(); } } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 3b5838ad81..b190e0c69a 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Services.Implement private IQuery _queryNotTrashed; //TODO: The non-lazy object should be injected private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); - + #region Constructors @@ -879,7 +879,7 @@ namespace Umbraco.Core.Services.Implement throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); } - if(content.Name != null && content.Name.Length > 255) + if (content.Name != null && content.Name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } @@ -1247,7 +1247,7 @@ namespace Umbraco.Core.Services.Implement if (culturesUnpublishing != null) { // This will mean that that we unpublished a mandatory culture or we unpublished the last culture. - + var langs = string.Join(", ", allLangs .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); @@ -1256,7 +1256,7 @@ namespace Umbraco.Core.Services.Implement if (publishResult == null) throw new PanicException("publishResult == null - should not happen"); - switch(publishResult.Result) + switch (publishResult.Result) { case PublishResultType.FailedPublishMandatoryCultureMissing: //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture) @@ -1270,7 +1270,7 @@ namespace Umbraco.Core.Services.Implement Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)"); return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content); } - + } Audit(AuditType.Unpublish, userId, content.Id); @@ -1290,7 +1290,7 @@ namespace Umbraco.Core.Services.Implement changeType = TreeChangeTypes.RefreshBranch; // whole branch else if (isNew == false && previouslyPublished) changeType = TreeChangeTypes.RefreshNode; // single node - + // invalidate the node/branch if (!branchOne) // for branches, handled by SaveAndPublishBranch @@ -1364,24 +1364,90 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable PerformScheduledPublish(DateTime date) - => PerformScheduledPublishInternal(date).ToList(); - - // beware! this method yields results, so the returned IEnumerable *must* be - // enumerated for anything to happen - dangerous, so private + exposed via - // the public method above, which forces ToList(). - private IEnumerable PerformScheduledPublishInternal(DateTime date) { + var allLangs = new Lazy>(() => _languageRepository.GetMany().ToList()); var evtMsgs = EventMessagesFactory.Get(); + var results = new List(); - using (var scope = ScopeProvider.CreateScope()) + PerformScheduledPublishingRelease(date, results, evtMsgs, allLangs); + PerformScheduledPublishingExpiration(date, results, evtMsgs, allLangs); + + return results; + } + + private void PerformScheduledPublishingExpiration(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) + { + using var scope = ScopeProvider.CreateScope(); + + // do a fast read without any locks since this executes often to see if we even need to proceed + if (_documentRepository.HasContentForExpiration(date)) { + // now take a write lock since we'll be updating scope.WriteLock(Constants.Locks.ContentTree); - var allLangs = _languageRepository.GetMany().ToList(); + foreach (var d in _documentRepository.GetContentForExpiration(date)) + { + if (d.ContentType.VariesByCulture()) + { + //find which cultures have pending schedules + var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date) + .Select(x => x.Culture) + .Distinct() + .ToList(); + + if (pendingCultures.Count == 0) + continue; //shouldn't happen but no point in processing this document if there's nothing there + + var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); + if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + { + results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); + continue; + } + + foreach (var c in pendingCultures) + { + //Clear this schedule for this culture + d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date); + //set the culture to be published + d.UnpublishCulture(c); + } + + var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); + if (result.Success == false) + Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + results.Add(result); + + } + else + { + //Clear this schedule + d.ContentSchedule.Clear(ContentScheduleAction.Expire, date); + var result = Unpublish(d, userId: d.WriterId); + if (result.Success == false) + Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); + results.Add(result); + } + } + + _documentRepository.ClearSchedule(date, ContentScheduleAction.Expire); + } + + scope.Complete(); + } + + private void PerformScheduledPublishingRelease(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) + { + using var scope = ScopeProvider.CreateScope(); + + // do a fast read without any locks since this executes often to see if we even need to proceed + if (_documentRepository.HasContentForRelease(date)) + { + // now take a write lock since we'll be updating + scope.WriteLock(Constants.Locks.ContentTree); foreach (var d in _documentRepository.GetContentForRelease(date)) { - PublishResult result; if (d.ContentType.VariesByCulture()) { //find which cultures have pending schedules @@ -1391,11 +1457,14 @@ namespace Umbraco.Core.Services.Implement .ToList(); if (pendingCultures.Count == 0) - break; //shouldn't happen but no point in continuing if there's nothing there + continue; //shouldn't happen but no point in processing this document if there's nothing there var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) - yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d); + { + results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); + continue; // this document is canceled move next + } var publishing = true; foreach (var culture in pendingCultures) @@ -1407,94 +1476,51 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed Property[] invalidProperties = null; - var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture)); + var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs.Value, culture)); var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); publishing &= tryPublish; //set the culture to be published - if (!publishing) break; // no point continuing + if (!publishing) continue; // move to next document } + PublishResult result; + if (d.Trashed) result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d); else if (!publishing) result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); else - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId); - + result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); if (result.Success == false) Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; + results.Add(result); } else { //Clear this schedule d.ContentSchedule.Clear(ContentScheduleAction.Release, date); - result = d.Trashed + var result = d.Trashed ? new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d) : SaveAndPublish(d, userId: d.WriterId); if (result.Success == false) Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; + results.Add(result); } } - foreach (var d in _documentRepository.GetContentForExpiration(date)) - { - PublishResult result; - if (d.ContentType.VariesByCulture()) - { - //find which cultures have pending schedules - var pendingCultures = d.ContentSchedule.GetPending(ContentScheduleAction.Expire, date) - .Select(x => x.Culture) - .Distinct() - .ToList(); + _documentRepository.ClearSchedule(date, ContentScheduleAction.Release); - if (pendingCultures.Count == 0) - break; //shouldn't happen but no point in continuing if there's nothing there - - var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) - yield return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d); - - foreach (var c in pendingCultures) - { - //Clear this schedule for this culture - d.ContentSchedule.Clear(c, ContentScheduleAction.Expire, date); - //set the culture to be published - d.UnpublishCulture(c); - } - - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId); - if (result.Success == false) - Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; - - } - else - { - //Clear this schedule - d.ContentSchedule.Clear(ContentScheduleAction.Expire, date); - result = Unpublish(d, userId: d.WriterId); - if (result.Success == false) - Logger.Error(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); - yield return result; - } - - - } - - _documentRepository.ClearSchedule(date); - - scope.Complete(); } + + scope.Complete(); } // utility 'PublishCultures' func used by SaveAndPublishBranch @@ -2650,7 +2676,7 @@ namespace Umbraco.Core.Services.Implement // there will be nothing to publish/unpublish. return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } - + // missing mandatory culture = cannot be published var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); @@ -3165,6 +3191,6 @@ namespace Umbraco.Core.Services.Implement #endregion - + } } diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index fdd2d9ceae..3d4c109bfb 100644 --- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -511,6 +511,16 @@ namespace Umbraco.Core.Services.Implement // delete content DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id)); + + // Next find all other document types that have a reference to this content type + var referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes.Any(p=>p.Id.Value==item.Id)); + foreach (var reference in referenceToAllowedContentTypes) + { + reference.AllowedContentTypes = reference.AllowedContentTypes.Where(p => p.Id.Value != item.Id); + var changedRef = new List>() { new ContentTypeChange(reference, ContentTypeChangeTypes.RefreshMain) }; + // Fire change event + OnChanged(scope, changedRef.ToEventArgs()); + } // finally delete the content type // - recursively deletes all descendants @@ -518,7 +528,7 @@ namespace Umbraco.Core.Services.Implement // (contents of any descendant type have been deleted but // contents of any composed (impacted) type remain but // need to have their property data cleared) - Repository.Delete(item); + Repository.Delete(item); //... var changes = descendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove)) diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index a037a83920..d83628a316 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Collections; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -15,19 +13,73 @@ namespace Umbraco.Core.Services { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; + private readonly ILocalizedTextService _textService; - public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + public PropertyValidationService(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; + _textService = textService; } //TODO: Remove this method in favor of the overload specifying all dependencies public PropertyValidationService() - : this(Current.PropertyEditors, Current.Services.DataTypeService) + : this(Current.PropertyEditors, Current.Services.DataTypeService, Current.Services.TextService) { } + public IEnumerable ValidatePropertyValue( + PropertyType propertyType, + object postedValue) + { + if (propertyType is null) throw new ArgumentNullException(nameof(propertyType)); + var dataType = _dataTypeService.GetDataType(propertyType.DataTypeId); + if (dataType == null) throw new InvalidOperationException("No data type found by id " + propertyType.DataTypeId); + + var editor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (editor == null) throw new InvalidOperationException("No property editor found by alias " + propertyType.PropertyEditorAlias); + + return ValidatePropertyValue(_textService, editor, dataType, postedValue, propertyType.Mandatory, propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage); + } + + internal static IEnumerable ValidatePropertyValue( + ILocalizedTextService textService, + IDataEditor editor, + IDataType dataType, + object postedValue, + bool isRequired, + string validationRegExp, + string isRequiredMessage, + string validationRegExpMessage) + { + // Retrieve default messages used for required and regex validatation. We'll replace these + // if set with custom ones if they've been provided for a given property. + var requiredDefaultMessages = new[] + { + textService.Localize("validation", "invalidNull"), + textService.Localize("validation", "invalidEmpty") + }; + var formatDefaultMessages = new[] + { + textService.Localize("validation", "invalidPattern"), + }; + + var valueEditor = editor.GetValueEditor(dataType.Configuration); + foreach (var validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp)) + { + // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). + if (isRequired && !string.IsNullOrWhiteSpace(isRequiredMessage) && requiredDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + validationResult.ErrorMessage = isRequiredMessage; + } + if (!string.IsNullOrWhiteSpace(validationRegExp) && !string.IsNullOrWhiteSpace(validationRegExpMessage) && formatDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + validationResult.ErrorMessage = validationRegExpMessage; + } + yield return validationResult; + } + } + /// /// Validates the content item's properties pass validation rules /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 17896d7785..e8180d3116 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -132,15 +132,27 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Examine/IndexRebuilder.cs b/src/Umbraco.Examine/IndexRebuilder.cs index 786aecac71..b14ff25c57 100644 --- a/src/Umbraco.Examine/IndexRebuilder.cs +++ b/src/Umbraco.Examine/IndexRebuilder.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Examine; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; namespace Umbraco.Examine { @@ -12,12 +14,20 @@ namespace Umbraco.Examine /// public class IndexRebuilder { + private readonly IProfilingLogger _logger; private readonly IEnumerable _populators; public IExamineManager ExamineManager { get; } + [Obsolete("Use constructor with all dependencies")] public IndexRebuilder(IExamineManager examineManager, IEnumerable populators) + : this(Current.ProfilingLogger, examineManager, populators) + { + } + + public IndexRebuilder(IProfilingLogger logger, IExamineManager examineManager, IEnumerable populators) { _populators = populators; + _logger = logger; ExamineManager = examineManager; } @@ -50,8 +60,18 @@ namespace Umbraco.Examine index.CreateIndex(); // clear the index } - //run the populators in parallel against all indexes - Parallel.ForEach(_populators, populator => populator.Populate(indexes)); + // run each populator over the indexes + foreach(var populator in _populators) + { + try + { + populator.Populate(indexes); + } + catch (Exception e) + { + _logger.Error(e, "Index populating failed for populator {Populator}", populator.GetType()); + } + } } } diff --git a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs index 0e125759c6..912d0e3363 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PureLiveModelFactory.cs @@ -542,8 +542,10 @@ namespace Umbraco.ModelsBuilder.Embedded if (modelInfos.TryGetValue(typeName, out var modelInfo)) throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); - // fixme use Core's ReflectionUtilities.EmitCtor !! + // TODO: use Core's ReflectionUtilities.EmitCtor !! // Yes .. DynamicMethod is uber slow + // TODO: But perhaps https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder?view=netcore-3.1 is better still? + // See CtorInvokeBenchmarks var meth = new DynamicMethod(string.Empty, typeof(IPublishedElement), ctorArgTypes, type.Module, true); var gen = meth.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); diff --git a/src/Umbraco.TestData/LoadTestController.cs b/src/Umbraco.TestData/LoadTestController.cs new file mode 100644 index 0000000000..97665dd084 --- /dev/null +++ b/src/Umbraco.TestData/LoadTestController.cs @@ -0,0 +1,371 @@ +using System; +using System.Threading; +using System.Linq; +using System.Web.Mvc; +using Umbraco.Core.Services; +using Umbraco.Core.Models; +using System.Web; +using System.Web.Hosting; +using System.Web.Routing; +using System.Diagnostics; +using Umbraco.Core.Composing; +using System.Configuration; + +// see https://github.com/Shazwazza/UmbracoScripts/tree/master/src/LoadTesting + +namespace Umbraco.TestData +{ + public class LoadTestController : Controller + { + public LoadTestController(ServiceContext serviceContext) + { + _serviceContext = serviceContext; + } + + private static readonly Random _random = new Random(); + private static readonly object _locko = new object(); + + private static volatile int _containerId = -1; + + private const string _containerAlias = "LoadTestContainer"; + private const string _contentAlias = "LoadTestContent"; + private const int _textboxDefinitionId = -88; + private const int _maxCreate = 1000; + + private static readonly string HeadHtml = @" + + LoadTest + + + +
+

LoadTest

+
" + System.Configuration.ConfigurationManager.AppSettings["umbracoConfigurationStatus"] + @"
+
+"; + + private const string FootHtml = @" +"; + + private static readonly string _containerTemplateText = @" +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; + var container = Umbraco.ContentAtRoot().OfTypes(""" + _containerAlias + @""").FirstOrDefault(); + var contents = container.Children().ToArray(); + var groups = contents.GroupBy(x => x.Value(""origin"")); + var id = contents.Length > 0 ? contents[0].Id : -1; + var wurl = Request.QueryString[""u""] == ""1""; + var missing = contents.Length > 0 && contents[contents.Length - 1].Id - contents[0].Id >= contents.Length; +} +" + HeadHtml + @" +
+@contents.Length items +
    +@foreach (var group in groups) +{ +
  • @group.Key: @group.Count()
  • +} +
+
+@foreach (var content in contents) +{ + while (content.Id > id) + { +
@id :: MISSING
+ id++; + } + if (wurl) + { +
@content.Id :: @content.Name :: @content.Url
+ } + else + { +
@content.Id :: @content.Name
+ } id++; +} +
+" + FootHtml; + private readonly ServiceContext _serviceContext; + + private ActionResult ContentHtml(string s) + { + return Content(HeadHtml + s + FootHtml); + } + + public ActionResult Index() + { + var res = EnsureInitialize(); + if (res != null) return res; + + var html = @"Welcome. You can: + +"; + + return ContentHtml(html); + } + + private ActionResult EnsureInitialize() + { + if (_containerId > 0) return null; + + lock (_locko) + { + if (_containerId > 0) return null; + + var contentTypeService = _serviceContext.ContentTypeService; + var contentType = contentTypeService.Get(_contentAlias); + if (contentType == null) + return ContentHtml("Not installed, first you must install."); + + var containerType = contentTypeService.Get(_containerAlias); + if (containerType == null) + return ContentHtml("Panic! Container type is missing."); + + var contentService = _serviceContext.ContentService; + var container = contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault(); + if (container == null) + return ContentHtml("Panic! Container is missing."); + + _containerId = container.Id; + return null; + } + } + + public ActionResult Install() + { + var dataTypeService = _serviceContext.DataTypeService; + + //var dataType = dataTypeService.GetAll(Constants.DataTypes.DefaultContentListView); + + + //if (!dict.ContainsKey("pageSize")) dict["pageSize"] = new PreValue("10"); + //dict["pageSize"].Value = "200"; + //dataTypeService.SavePreValues(dataType, dict); + + var contentTypeService = _serviceContext.ContentTypeService; + + var contentType = new ContentType(-1) + { + Alias = _contentAlias, + Name = "LoadTest Content", + Description = "Content for LoadTest", + Icon = "icon-document" + }; + var def = _serviceContext.DataTypeService.GetDataType(_textboxDefinitionId); + contentType.AddPropertyType(new PropertyType(def) + { + Name = "Origin", + Alias = "origin", + Description = "The origin of the content.", + }); + contentTypeService.Save(contentType); + + var containerTemplate = ImportTemplate(_serviceContext, + "LoadTestContainer", "LoadTestContainer", _containerTemplateText); + + var containerType = new ContentType(-1) + { + Alias = _containerAlias, + Name = "LoadTest Container", + Description = "Container for LoadTest content", + Icon = "icon-document", + AllowedAsRoot = true, + IsContainer = true + }; + containerType.AllowedContentTypes = containerType.AllowedContentTypes.Union(new[] + { + new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias), + }); + containerType.AllowedTemplates = containerType.AllowedTemplates.Union(new[] { containerTemplate }); + containerType.SetDefaultTemplate(containerTemplate); + contentTypeService.Save(containerType); + + var contentService = _serviceContext.ContentService; + var content = contentService.Create("LoadTestContainer", -1, _containerAlias); + contentService.SaveAndPublish(content); + + return ContentHtml("Installed."); + } + + public ActionResult Create(int n = 1, int r = 0, string o = null) + { + var res = EnsureInitialize(); + if (res != null) return res; + + if (r < 0) r = 0; + if (r > 100) r = 100; + var restart = GetRandom(0, 100) > (100 - r); + + var contentService = _serviceContext.ContentService; + + if (n < 1) n = 1; + if (n > _maxCreate) n = _maxCreate; + for (int i = 0; i < n; i++) + { + var name = Guid.NewGuid().ToString("N").ToUpper() + "-" + (restart ? "R" : "X") + "-" + o; + var content = contentService.Create(name, _containerId, _contentAlias); + content.SetValue("origin", o); + contentService.SaveAndPublish(content); + } + + if (restart) + DoRestart(); + + return ContentHtml("Created " + n + " content" + + (restart ? ", and restarted" : "") + + "."); + } + + private int GetRandom(int minValue, int maxValue) + { + lock (_locko) + { + return _random.Next(minValue, maxValue); + } + } + + public ActionResult Clear() + { + var res = EnsureInitialize(); + if (res != null) return res; + + var contentType = _serviceContext.ContentTypeService.Get(_contentAlias); + _serviceContext.ContentService.DeleteOfType(contentType.Id); + + return ContentHtml("Cleared."); + } + + private void DoRestart() + { + HttpContext.User = null; + System.Web.HttpContext.Current.User = null; + Thread.CurrentPrincipal = null; + HttpRuntime.UnloadAppDomain(); + } + + public ActionResult Restart() + { + DoRestart(); + + return ContentHtml("Restarted."); + } + + public ActionResult Die() + { + var timer = new System.Threading.Timer(_ => + { + throw new Exception("die!"); + }); + timer.Change(100, 0); + + return ContentHtml("Dying."); + } + + public ActionResult Domains() + { + var currentDomain = AppDomain.CurrentDomain; + var currentName = currentDomain.FriendlyName; + var pos = currentName.IndexOf('-'); + if (pos > 0) currentName = currentName.Substring(0, pos); + + var text = new System.Text.StringBuilder(); + text.Append("
Process ID: " + Process.GetCurrentProcess().Id + "
"); + text.Append("
"); + text.Append("
IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "
"); + text.Append("
App ID: " + currentName + "
"); + //text.Append("
AppPool: " + Zbu.WebManagement.AppPoolHelper.GetCurrentApplicationPoolName() + "
"); + text.Append("
"); + + text.Append("
Domains:
    "); + text.Append("
  • Not implemented.
  • "); + /* + foreach (var domain in Zbu.WebManagement.AppDomainHelper.GetAppDomains().OrderBy(x => x.Id)) + { + var name = domain.FriendlyName; + pos = name.IndexOf('-'); + if (pos > 0) name = name.Substring(0, pos); + text.Append("
  • " + +"[" + domain.Id + "] " + name + + (domain.IsDefaultAppDomain() ? " (default)" : "") + + (domain.Id == currentDomain.Id ? " (current)" : "") + + "
  • "); + } + */ + text.Append("
"); + + return ContentHtml(text.ToString()); + } + + public ActionResult Recycle() + { + return ContentHtml("Not implemented—please use IIS console."); + } + + private static Template ImportTemplate(ServiceContext svces, string name, string alias, string text, ITemplate master = null) + { + var t = new Template(name, alias) { Content = text }; + if (master != null) + t.SetMasterTemplate(master); + svces.FileService.SaveTemplate(t); + return t; + } + } + + public class TestComponent : IComponent + { + public void Initialize() + { + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return; + + RouteTable.Routes.MapRoute( + name: "LoadTest", + url: "LoadTest/{action}", + defaults: new + { + controller = "LoadTest", + action = "Index" + }, + namespaces: new[] { "Umbraco.TestData" } + ); + } + + public void Terminate() + { + } + } + + public class TestComposer : ComponentComposer, IUserComposer + { + public override void Compose(Composition composition) + { + base.Compose(composition); + + if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true") + return; + + composition.Register(typeof(LoadTestController), Lifetime.Request); + } + } +} diff --git a/src/Umbraco.TestData/Umbraco.TestData.csproj b/src/Umbraco.TestData/Umbraco.TestData.csproj index d61321ebb8..a3753cc17b 100644 --- a/src/Umbraco.TestData/Umbraco.TestData.csproj +++ b/src/Umbraco.TestData/Umbraco.TestData.csproj @@ -41,6 +41,7 @@ + diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/memberGroups.js b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/memberGroups.js new file mode 100644 index 0000000000..be9b93134d --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/memberGroups.js @@ -0,0 +1,32 @@ +context('User Groups', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create member group', () => { + const name = "Test Group"; + + cy.umbracoEnsureMemberGroupNameNotExists(name); + + cy.umbracoSection('member'); + cy.get('li .umb-tree-root:contains("Members")').should("be.visible"); + + cy.umbracoTreeItem("member", ["Member Groups"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + // Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureMemberGroupNameNotExists(name); + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js new file mode 100644 index 0000000000..aeb4576ccd --- /dev/null +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Members/members.js @@ -0,0 +1,41 @@ +/// +context('Members', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + it('Create member', () => { + const name = "Alice Bobson"; + const email = "alice-bobson@acceptancetest.umbraco"; + const password = "$AUlkoF*St0kgPiyyVEk5iU5JWdN*F7&@OSl5Y4pOofnidfifkBj5Ns2ONv%FzsTl36V1E924Gw97zcuSeT7UwK&qb5l&O9h!d!w"; + + cy.umbracoEnsureMemberEmailNotExists(email); + cy.umbracoSection('member'); + cy.get('li .umb-tree-root:contains("Members")').should("be.visible"); + + cy.umbracoTreeItem("member", ["Members"]).rightclick(); + + cy.umbracoContextMenuAction("action-create").click(); + cy.get('.menu-label').first().click(); + + //Type name + cy.umbracoEditorHeaderName(name); + + cy.get('input#_umb_login').clear().type(email); + cy.get('input#_umb_email').clear().type(email); + cy.get('input#password').clear().type(password); + cy.get('input#confirmPassword').clear().type(password); + + // Save + cy.get('.btn-success').click(); + + //Assert + cy.umbracoSuccessNotification().should('be.visible'); + + //Clean up + cy.umbracoEnsureMemberEmailNotExists(email); + + }); + +}); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts index 5a5ba0b3e0..e0a651731a 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts @@ -34,7 +34,7 @@ context('Document Types', () => { cy.get('.umb-search-field').type('Textstring'); // Choose first item - cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); // Save property cy.get('.btn-success').last().click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts index a963da754f..646654bbab 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts @@ -34,7 +34,7 @@ context('Media Types', () => { cy.get('.umb-search-field').type('Textstring'); // Choose first item - cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); // Save property cy.get('.btn-success').last().click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts index 53823f2a69..fe2d88d64f 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts @@ -32,7 +32,7 @@ context('Member Types', () => { cy.get('.umb-search-field').type('Textstring'); // Choose first item - cy.get('ul.umb-card-grid li a[title="Textstring"]').closest("li").click(); + cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); // Save property cy.get('.btn-success').last().click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.js similarity index 99% rename from src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts rename to src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.js index 905d0fc25c..afef0e7701 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Users/userGroups.js @@ -1,4 +1,3 @@ - context('User Groups', () => { beforeEach(() => { diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index ad125d090a..3b4177ce3f 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -6,8 +6,8 @@ "devDependencies": { "cross-env": "^7.0.2", "ncp": "^2.0.0", - "cypress": "^4.6.0", - "umbraco-cypress-testhelpers": "1.0.0-beta-39" + "cypress": "^4.9.0", + "umbraco-cypress-testhelpers": "1.0.0-beta-44" }, "dependencies": { "typescript": "^3.9.2" diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 8d15613791..37fe952851 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -16,6 +16,8 @@ namespace Umbraco.Tests.Benchmarks // - it's faster to get+invoke the ctor // - emitting the ctor is unless if invoked only 1 + // TODO: Check out https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder?view=netcore-3.1 ? + //[Config(typeof(Config))] [MemoryDiagnoser] public class CtorInvokeBenchmarks diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index f3d9f895ef..2346740ffb 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -42,9 +42,9 @@ namespace Umbraco.Tests.Cache.PublishedCache protected override void Initialize() { base.Initialize(); - var type = new AutoPublishedContentType(22, "myType", new PublishedPropertyType[] { }); - var image = new AutoPublishedContentType(23, "Image", new PublishedPropertyType[] { }); - var testMediaType = new AutoPublishedContentType(24, "TestMediaType", new PublishedPropertyType[] { }); + var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "myType", new PublishedPropertyType[] { }); + var image = new AutoPublishedContentType(Guid.NewGuid(), 23, "Image", new PublishedPropertyType[] { }); + var testMediaType = new AutoPublishedContentType(Guid.NewGuid(), 24, "TestMediaType", new PublishedPropertyType[] { }); _mediaTypes = new Dictionary { { type.Alias, type }, diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7459ae848b..9cd4f39c17 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -268,7 +268,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(38, types.Count()); + Assert.AreEqual(39, types.Count()); } /// diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs index 8ce6b10983..48f0e7b27e 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedContentCache.cs @@ -14,7 +14,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Tests.LegacyXmlPublishedCache { - internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache + internal class PublishedContentCache : PublishedCacheBase, IPublishedContentCache2 { private readonly IAppCache _appCache; private readonly IGlobalSettings _globalSettings; @@ -532,15 +532,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override IPublishedContentType GetContentType(int id) - { - return _contentTypeCache.Get(PublishedItemType.Content, id); - } + public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Content, id); - public override IPublishedContentType GetContentType(string alias) - { - return _contentTypeCache.Get(PublishedItemType.Content, alias); - } + public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Content, alias); + + public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Content, key); #endregion } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs index 999d7f040d..56033e6b0a 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/PublishedMediaCache.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. /// - internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache + internal class PublishedMediaCache : PublishedCacheBase, IPublishedMediaCache2 { private readonly IMediaService _mediaService; private readonly IUserService _userService; @@ -612,15 +612,11 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Content types - public override IPublishedContentType GetContentType(int id) - { - return _contentTypeCache.Get(PublishedItemType.Media, id); - } + public override IPublishedContentType GetContentType(int id) => _contentTypeCache.Get(PublishedItemType.Media, id); - public override IPublishedContentType GetContentType(string alias) - { - return _contentTypeCache.Get(PublishedItemType.Media, alias); - } + public override IPublishedContentType GetContentType(string alias) => _contentTypeCache.Get(PublishedItemType.Media, alias); + + public override IPublishedContentType GetContentType(Guid key) => _contentTypeCache.Get(PublishedItemType.Media, key); public override IEnumerable GetByContentType(IPublishedContentType contentType) { diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index ab5f726894..b87ff499b6 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -442,7 +442,10 @@ namespace Umbraco.Tests.Models [Test] public void ContentPublishValuesWithMixedPropertyTypeVariations() { - var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance(), Current.Factory.GetInstance().DataTypeService); + var propertyValidationService = new PropertyValidationService( + Current.Factory.GetInstance(), + Current.Factory.GetInstance().DataTypeService, + Current.Factory.GetInstance().TextService); const string langFr = "fr-FR"; // content type varies by Culture @@ -574,7 +577,10 @@ namespace Umbraco.Tests.Models prop.SetValue("a"); Assert.AreEqual("a", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - var propertyValidationService = new PropertyValidationService(Current.Factory.GetInstance(), Current.Factory.GetInstance().DataTypeService); + var propertyValidationService = new PropertyValidationService( + Current.Factory.GetInstance(), + Current.Factory.GetInstance().DataTypeService, + Current.Factory.GetInstance().TextService); Assert.IsTrue(propertyValidationService.IsPropertyValid(prop)); diff --git a/src/Umbraco.Tests/PropertyEditors/BlockListPropertyValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/BlockListPropertyValueConverterTests.cs new file mode 100644 index 0000000000..23cc782106 --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -0,0 +1,449 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PropertyEditors; +using Umbraco.Web.PropertyEditors.ValueConverters; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class BlockListPropertyValueConverterTests + { + private readonly Guid ContentKey1 = Guid.NewGuid(); + private readonly Guid ContentKey2 = Guid.NewGuid(); + private const string ContentAlias1 = "Test1"; + private const string ContentAlias2 = "Test2"; + private readonly Guid SettingKey1 = Guid.NewGuid(); + private readonly Guid SettingKey2 = Guid.NewGuid(); + private const string SettingAlias1 = "Setting1"; + private const string SettingAlias2 = "Setting2"; + + /// + /// Setup mocks for IPublishedSnapshotAccessor + /// + /// + private IPublishedSnapshotAccessor GetPublishedSnapshotAccessor() + { + var test1ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == ContentKey1 + && x.Alias == ContentAlias1); + var test2ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == ContentKey2 + && x.Alias == ContentAlias2); + var test3ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == SettingKey1 + && x.Alias == SettingAlias1); + var test4ContentType = Mock.Of(x => + x.IsElement == true + && x.Key == SettingKey2 + && x.Alias == SettingAlias2); + var contentCache = new Mock(); + contentCache.Setup(x => x.GetContentType(ContentKey1)).Returns(test1ContentType); + contentCache.Setup(x => x.GetContentType(ContentKey2)).Returns(test2ContentType); + contentCache.Setup(x => x.GetContentType(SettingKey1)).Returns(test3ContentType); + contentCache.Setup(x => x.GetContentType(SettingKey2)).Returns(test4ContentType); + var publishedSnapshot = Mock.Of(x => x.Content == contentCache.Object); + var publishedSnapshotAccessor = Mock.Of(x => x.PublishedSnapshot == publishedSnapshot); + return publishedSnapshotAccessor; + } + + private BlockListPropertyValueConverter CreateConverter() + { + var publishedSnapshotAccessor = GetPublishedSnapshotAccessor(); + var publishedModelFactory = new NoopPublishedModelFactory(); + var editor = new BlockListPropertyValueConverter( + Mock.Of(), + new BlockEditorConverter(publishedSnapshotAccessor, publishedModelFactory)); + return editor; + } + + private BlockListConfiguration ConfigForMany() => new BlockListConfiguration + { + Blocks = new[] { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey1, + SettingsElementTypeKey = SettingKey2 + }, + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey2, + SettingsElementTypeKey = SettingKey1 + } + } + }; + + private BlockListConfiguration ConfigForSingle() => new BlockListConfiguration + { + Blocks = new[] { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey1 + } + } + }; + + private IPublishedPropertyType GetPropertyType(BlockListConfiguration config) + { + var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); + var propertyType = Mock.Of(x => + x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList + && x.DataType == dataType); + return propertyType; + } + + [Test] + public void Is_Converter_For() + { + var editor = CreateConverter(); + Assert.IsTrue(editor.IsConverter(Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList))); + Assert.IsFalse(editor.IsConverter(Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.NestedContent))); + } + + [Test] + public void Get_Value_Type_Multiple() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + + var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); + var propType = Mock.Of(x => x.DataType == dataType); + + var valueType = editor.GetPropertyValueType(propType); + + // the result is always block list model + Assert.AreEqual(typeof(BlockListModel), valueType); + } + + [Test] + public void Get_Value_Type_Single() + { + var editor = CreateConverter(); + var config = ConfigForSingle(); + + var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); + var propType = Mock.Of(x => x.DataType == dataType); + + var valueType = editor.GetPropertyValueType(propType); + + // the result is always block list model + Assert.AreEqual(typeof(BlockListModel), valueType); + } + + [Test] + public void Convert_Null_Empty() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + string json = null; + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + + json = string.Empty; + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + } + + [Test] + public void Convert_Valid_Empty_Json() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = "{}"; + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + + json = @"{ +layout: {}, +data: []}"; + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + + // Even though there is a layout, there is no data, so the conversion will result in zero elements in total + json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9' + } + ] + }, + contentData: [] +}"; + + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + + // Even though there is a layout and data, the data is invalid (missing required keys) so the conversion will result in zero elements in total + json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9' + } + ] + }, + contentData: [ + { + 'udi': 'umb://element/e7dba547615b4e9ab4ab2a7674845bc9' + } + ] +}"; + + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(0, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + + // Everthing is ok except the udi reference in the layout doesn't match the data so it will be empty + json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'key': '1304E1DD-0000-4396-84FE-8A399231CB3D' + } + ] +}"; + + converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(1, converted.ContentData.Count()); + Assert.AreEqual(0, converted.Layout.Count()); + } + + [Test] + public void Convert_Valid_Json() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + } + ] +}"; + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(1, converted.ContentData.Count()); + var item0 = converted.ContentData.ElementAt(0); + Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key); + Assert.AreEqual("Test1", item0.ContentType.Alias); + Assert.AreEqual(1, converted.Layout.Count()); + var layout0 = converted.Layout.ElementAt(0); + Assert.IsNull(layout0.Settings); + Assert.AreEqual(Udi.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), layout0.ContentUdi); + } + + [Test] + public void Get_Data_From_Layout_Item() + { + var editor = CreateConverter(); + var config = ConfigForMany(); + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D', + 'settingsUdi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentUdi': 'umb://element/0A4A416E547D464FABCC6F345C17809A', + 'settingsUdi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/E05A034704424AB3A520E048E6197E79' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A' + } + ], + settingsData: [ + { + 'contentTypeKey': '" + SettingKey1 + @"', + 'udi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/BCF4BA3DA40C496C93EC58FAC85F18B9' + } + ], +}"; + + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(3, converted.ContentData.Count()); + Assert.AreEqual(3, converted.SettingsData.Count()); + Assert.AreEqual(2, converted.Layout.Count()); + + var item0 = converted.Layout.ElementAt(0); + Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Content.Key); + Assert.AreEqual("Test1", item0.Content.ContentType.Alias); + Assert.AreEqual(Guid.Parse("1F613E26CE274898908A561437AF5100"), item0.Settings.Key); + Assert.AreEqual("Setting2", item0.Settings.ContentType.Alias); + + var item1 = converted.Layout.ElementAt(1); + Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item1.Content.Key); + Assert.AreEqual("Test2", item1.Content.ContentType.Alias); + Assert.AreEqual(Guid.Parse("63027539B0DB45E7B70459762D4E83DD"), item1.Settings.Key); + Assert.AreEqual("Setting1", item1.Settings.ContentType.Alias); + + } + + [Test] + public void Data_Item_Removed_If_Removed_From_Config() + { + var editor = CreateConverter(); + + // The data below expects that ContentKey1 + ContentKey2 + SettingsKey1 + SettingsKey2 exist but only ContentKey2 exists so + // the data should all be filtered. + var config = new BlockListConfiguration + { + Blocks = new[] { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = ContentKey2, + SettingsElementTypeKey = null + } + } + }; + + var propertyType = GetPropertyType(config); + var publishedElement = Mock.Of(); + + var json = @" +{ + layout: { + '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ + { + 'contentUdi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D', + 'settingsUdi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentUdi': 'umb://element/0A4A416E547D464FABCC6F345C17809A', + 'settingsUdi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + } + ] + }, + contentData: [ + { + 'contentTypeKey': '" + ContentKey1 + @"', + 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/E05A034704424AB3A520E048E6197E79' + }, + { + 'contentTypeKey': '" + ContentKey2 + @"', + 'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A' + } + ], + settingsData: [ + { + 'contentTypeKey': '" + SettingKey1 + @"', + 'udi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/1F613E26CE274898908A561437AF5100' + }, + { + 'contentTypeKey': '" + SettingKey2 + @"', + 'udi': 'umb://element/BCF4BA3DA40C496C93EC58FAC85F18B9' + } + ], +}"; + + var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; + + Assert.IsNotNull(converted); + Assert.AreEqual(2, converted.ContentData.Count()); + Assert.AreEqual(0, converted.SettingsData.Count()); + Assert.AreEqual(1, converted.Layout.Count()); + + var item0 = converted.Layout.ElementAt(0); + Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item0.Content.Key); + Assert.AreEqual("Test2", item0.Content.ContentType.Alias); + Assert.IsNull(item0.Settings); + + } + + } +} diff --git a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs new file mode 100644 index 0000000000..1b83c048d2 --- /dev/null +++ b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -0,0 +1,405 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Web.Compose; + +namespace Umbraco.Tests.PropertyEditors +{ + [TestFixture] + public class NestedContentPropertyComponentTests + { + [Test] + public void Invalid_Json() + { + var component = new NestedContentPropertyComponent(); + + Assert.DoesNotThrow(() => component.CreateNestedContentKeys("this is not json", true)); + } + + [Test] + public void No_Nesting() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = @"[ + {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, + {""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} +]"; + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + [Test] + public void One_Level_Nesting_Unescaped() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"": [{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ] + } +]"; + + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()) + .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) + .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + [Test] + public void One_Level_Nesting_Escaped() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + subJsonEscaped + @" + } +]"; + + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()) + .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) + .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + [Test] + public void Nested_In_Complex_Editor_Escaped() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + // Complex editor such as the grid + var complexEditorJsonEscaped = @"{ + ""name"": ""1 column layout"", + ""sections"": [ + { + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [ + { + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [ + { + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subJsonEscaped + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] +}"; + + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + complexEditorJsonEscaped + @" + } +]"; + + var expected = json + .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) + .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()) + .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) + .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, false, guidFactory); + + Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); + } + + + [Test] + public void No_Nesting_Generates_Keys_For_Missing_Items() + { + var guids = new[] { Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + var json = @"[ + {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, + {""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} +]"; + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, true, guidFactory); + + // Ensure the new GUID is put in a key into the JSON + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + + // Ensure that the original key is NOT changed/modified & still exists + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08")); + } + + [Test] + public void One_Level_Nesting_Escaped_Generates_Keys_For_Missing_Items() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Nested Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + var json = @"[{ + ""name"": ""Item 1 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"", + ""name"": ""Item 2"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + subJsonEscaped + @" + } +]"; + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, true, guidFactory); + + // Ensure the new GUID is put in a key into the JSON for each item + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString())); + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[2].ToString())); + } + + [Test] + public void Nested_In_Complex_Editor_Escaped_Generates_Keys_For_Missing_Items() + { + var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; + var guidCounter = 0; + Func guidFactory = () => guids[guidCounter++]; + + // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing + // and this is how to do that, the result will also include quotes around it. + var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{ + ""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Nested Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""zoot"" + } + ]").ToString()); + + // Complex editor such as the grid + var complexEditorJsonEscaped = @"{ + ""name"": ""1 column layout"", + ""sections"": [ + { + ""grid"": ""12"", + ""rows"": [ + { + ""name"": ""Article"", + ""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"", + ""areas"": [ + { + ""grid"": ""4"", + ""controls"": [ + { + ""value"": ""I am quote"", + ""editor"": { + ""alias"": ""quote"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }, + { + ""grid"": ""8"", + ""controls"": [ + { + ""value"": ""Header"", + ""editor"": { + ""alias"": ""headline"", + ""view"": ""textstring"" + }, + ""styles"": null, + ""config"": null + }, + { + ""value"": " + subJsonEscaped + @", + ""editor"": { + ""alias"": ""madeUpNestedContent"", + ""view"": ""madeUpNestedContentInGrid"" + }, + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }], + ""styles"": null, + ""config"": null + }] + }] +}"; + + + var json = @"[{ + ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", + ""name"": ""Item 1"", + ""ncContentTypeAlias"": ""text"", + ""text"": ""woot"" + }, { + ""name"": ""Item 2 was copied and has no key"", + ""ncContentTypeAlias"": ""list"", + ""text"": ""zoot"", + ""subItems"":" + complexEditorJsonEscaped + @" + } +]"; + + var component = new NestedContentPropertyComponent(); + var result = component.CreateNestedContentKeys(json, true, guidFactory); + + // Ensure the new GUID is put in a key into the JSON for each item + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); + Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString())); + } + } +} diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs index 43c1a83d33..a9e3e8b9db 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.PropertyEditors }))); var publishedPropType = new PublishedPropertyType( - new PublishedContentType(1234, "test", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing), + new PublishedContentType(Guid.NewGuid(), 1234, "test", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing), new PropertyType("test", ValueStorageType.Nvarchar) { DataTypeId = 123 }, new PropertyValueConverterCollection(Enumerable.Empty()), Mock.Of(), mockPublishedContentTypeFactory.Object); diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 671129848c..3c60f4ddd0 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); + var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); @@ -74,7 +74,7 @@ namespace Umbraco.Tests.Published => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); public Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (int); + => typeof(int); public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; @@ -83,10 +83,10 @@ namespace Umbraco.Tests.Published => int.TryParse(source as string, out int i) ? i : 0; public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => (int) inter; + => (int)inter; public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => ((int) inter).ToString(); + => ((int)inter).ToString(); } #endregion @@ -120,11 +120,11 @@ namespace Umbraco.Tests.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", CreatePropertyTypes); + var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); - var cntType1 = contentTypeFactory.CreateContentType(1001, "cnt1", t => Enumerable.Empty()); + var cntType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "cnt1", t => Enumerable.Empty()); var cnt1 = new SolidPublishedContent(cntType1) { Id = 1234 }; cacheContent[cnt1.Id] = cnt1; @@ -143,7 +143,7 @@ namespace Umbraco.Tests.Published } public bool? IsValue(object value, PropertyValueLevel level) - => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); @@ -162,10 +162,10 @@ namespace Umbraco.Tests.Published => int.TryParse(source as string, out int i) ? i : -1; public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int) inter); + => _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById((int)inter); public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => ((int) inter).ToString(); + => ((int)inter).ToString(); } #endregion @@ -215,10 +215,10 @@ namespace Umbraco.Tests.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop" + i, i); } - var elementType1 = contentTypeFactory.CreateContentType(1000, "element1", t => CreatePropertyTypes(t, 1)); - var elementType2 = contentTypeFactory.CreateContentType(1001, "element2", t => CreatePropertyTypes(t, 2)); - var contentType1 = contentTypeFactory.CreateContentType(1002, "content1", t => CreatePropertyTypes(t, 1)); - var contentType2 = contentTypeFactory.CreateContentType(1003, "content2", t => CreatePropertyTypes(t, 2)); + var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", t => CreatePropertyTypes(t, 1)); + var elementType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "element2", t => CreatePropertyTypes(t, 2)); + var contentType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1002, "content1", t => CreatePropertyTypes(t, 1)); + var contentType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1003, "content2", t => CreatePropertyTypes(t, 2)); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "val1" } }, false); var element2 = new PublishedElement(elementType2, Guid.NewGuid(), new Dictionary { { "prop2", "1003" } }, false); @@ -239,22 +239,22 @@ namespace Umbraco.Tests.Published // can get the actual property Clr type // ie ModelType gets properly mapped by IPublishedContentModelFactory // must test ModelClrType with special equals 'cos they are not ref-equals - Assert.IsTrue(ModelType.Equals(typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType)); - Assert.AreEqual(typeof (IEnumerable), contentType2.GetPropertyType("prop2").ClrType); + Assert.IsTrue(ModelType.Equals(typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType)); + Assert.AreEqual(typeof(IEnumerable), contentType2.GetPropertyType("prop2").ClrType); // can create a model for an element var model1 = factory.CreateModel(element1); Assert.IsInstanceOf(model1); - Assert.AreEqual("val1", ((PublishedSnapshotTestObjects.TestElementModel1) model1).Prop1); + Assert.AreEqual("val1", ((PublishedSnapshotTestObjects.TestElementModel1)model1).Prop1); // can create a model for a published content var model2 = factory.CreateModel(element2); Assert.IsInstanceOf(model2); - var mmodel2 = (PublishedSnapshotTestObjects.TestElementModel2) model2; + var mmodel2 = (PublishedSnapshotTestObjects.TestElementModel2)model2; // and get direct property Assert.IsInstanceOf(model2.Value("prop2")); - Assert.AreEqual(1, ((PublishedSnapshotTestObjects.TestContentModel1[]) model2.Value("prop2")).Length); + Assert.AreEqual(1, ((PublishedSnapshotTestObjects.TestContentModel1[])model2.Value("prop2")).Length); // and get model property Assert.IsInstanceOf>(mmodel2.Prop2); @@ -271,7 +271,7 @@ namespace Umbraco.Tests.Published => propertyType.EditorAlias == "Umbraco.Void"; public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + => typeof(string); public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; @@ -290,7 +290,7 @@ namespace Umbraco.Tests.Published => propertyType.EditorAlias == "Umbraco.Void.2"; public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IEnumerable<>).MakeGenericType(ModelType.For("content1")); + => typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")); public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; @@ -303,7 +303,7 @@ namespace Umbraco.Tests.Published public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { - return ((int[]) inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1) _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray(); + return ((int[])inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1)_publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(x)).ToArray(); } } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index adfb9d3b6b..c5cee8843f 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Published var proflog = new ProfilingLogger(logger, profiler); PropertyEditorCollection editors = null; - var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors), Mock.Of(), Mock.Of()); + var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors), Mock.Of(), Mock.Of(), Mock.Of()); editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor })); var dataType1 = new DataType(editor) @@ -100,8 +100,8 @@ namespace Umbraco.Tests.Published .Returns((string alias) => { return alias == "contentN1" - ? (IList) new List() - : (IList) new List(); + ? (IList)new List() + : (IList)new List(); }); var contentCache = new Mock(); @@ -142,9 +142,9 @@ namespace Umbraco.Tests.Published yield return factory.CreatePropertyType(contentType, "propertyN1", 3); } - var contentType1 = factory.CreateContentType(1, "content1", CreatePropertyTypes1); - var contentType2 = factory.CreateContentType(2, "content2", CreatePropertyTypes2); - var contentTypeN1 = factory.CreateContentType(2, "contentN1", CreatePropertyTypesN1, isElement: true); + var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "content1", CreatePropertyTypes1); + var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "content2", CreatePropertyTypes2); + var contentTypeN1 = factory.CreateContentType(Guid.NewGuid(), 2, "contentN1", CreatePropertyTypesN1, isElement: true); // mocked content cache returns content types contentCache @@ -164,7 +164,7 @@ namespace Umbraco.Tests.Published (var contentType1, _) = CreateContentTypes(); // nested single converter returns the proper value clr type TestModel, and cache level - Assert.AreEqual(typeof (TestElementModel), contentType1.GetPropertyType("property1").ClrType); + Assert.AreEqual(typeof(TestElementModel), contentType1.GetPropertyType("property1").ClrType); Assert.AreEqual(PropertyCacheLevel.Element, contentType1.GetPropertyType("property1").CacheLevel); var key = Guid.NewGuid(); @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Published var content = new SolidPublishedContent(contentType1) { Key = key, - Properties = new [] + Properties = new[] { new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} @@ -183,7 +183,7 @@ namespace Umbraco.Tests.Published // nested single converter returns proper TestModel value Assert.IsInstanceOf(value); - var valueM = (TestElementModel) value; + var valueM = (TestElementModel)value; Assert.AreEqual("foo", valueM.PropValue); Assert.AreEqual(keyA, valueM.Key); } @@ -194,7 +194,7 @@ namespace Umbraco.Tests.Published (_, var contentType2) = CreateContentTypes(); // nested many converter returns the proper value clr type IEnumerable, and cache level - Assert.AreEqual(typeof (IEnumerable), contentType2.GetPropertyType("property2").ClrType); + Assert.AreEqual(typeof(IEnumerable), contentType2.GetPropertyType("property2").ClrType); Assert.AreEqual(PropertyCacheLevel.Element, contentType2.GetPropertyType("property2").CacheLevel); var key = Guid.NewGuid(); @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Published // nested many converter returns proper IEnumerable value Assert.IsInstanceOf>(value); Assert.IsInstanceOf>(value); - var valueM = ((IEnumerable) value).ToArray(); + var valueM = ((IEnumerable)value).ToArray(); Assert.AreEqual("foo", valueM[0].PropValue); Assert.AreEqual(keyA, valueM[0].Key); Assert.AreEqual("bar", valueM[1].PropValue); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 9db539d142..a795ca433e 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -41,7 +41,7 @@ namespace Umbraco.Tests.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); + var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); // PublishedElementPropertyBase.GetCacheLevels: // @@ -122,7 +122,7 @@ namespace Umbraco.Tests.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); + var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); var elementsCache = new FastDictionaryAppCache(); var snapshotCache = new FastDictionaryAppCache(); @@ -199,7 +199,7 @@ namespace Umbraco.Tests.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); + var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); Assert.Throws(() => { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index cc455b8e5d..7277f75be2 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -98,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetContent(true, 1); //change a doc type alias var c = (SolidPublishedContent)doc.Children.ElementAt(0); - c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + c.ContentType = new PublishedContentType(Guid.NewGuid(), 22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -129,7 +129,7 @@ namespace Umbraco.Tests.PublishedContent var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); var contentTypeAlias = createChildren ? "Parent" : "Child"; - var contentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var contentType = new PublishedContentType(Guid.NewGuid(), 22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var d = new SolidPublishedContent(contentType) { CreateDate = DateTime.Now, diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index 62447742ff..636f8502ed 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -73,14 +73,14 @@ namespace Umbraco.Tests.PublishedContent yield return factory.CreatePropertyType(contentType, "noprop", 1, variations: ContentVariation.Culture); } - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); + var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes1); IEnumerable CreatePropertyTypes2(IPublishedContentType contentType) { yield return factory.CreatePropertyType(contentType, "prop3", 1, variations: ContentVariation.Culture); } - var contentType2 = factory.CreateContentType(2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); + var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "contentType2", Enumerable.Empty(), CreatePropertyTypes2); var prop1 = new SolidPublishedPropertyWithLanguageVariants { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index 440474ae74..e2a1721d26 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -7,6 +7,7 @@ using Umbraco.Web; using Umbraco.Core; using Umbraco.Tests.Testing; using Umbraco.Web.Composing; +using System; namespace Umbraco.Tests.PublishedContent { @@ -21,9 +22,9 @@ namespace Umbraco.Tests.PublishedContent yield return factory.CreatePropertyType(contentType, "prop1", 1); } - var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); - var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); - var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); + var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "ContentType1", Enumerable.Empty(), CreatePropertyTypes); + var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "ContentType2", Enumerable.Empty(), CreatePropertyTypes); + var contentType2Sub = factory.CreateContentType(Guid.NewGuid(), 3, "ContentType2Sub", Enumerable.Empty(), CreatePropertyTypes); var content = new SolidPublishedContent(contentType1) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index 59791fc645..7e0d0f332e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.Templates; using Umbraco.Web.Models; +using System; namespace Umbraco.Tests.PublishedContent { @@ -56,7 +57,7 @@ namespace Umbraco.Tests.PublishedContent yield return publishedContentTypeFactory.CreatePropertyType(contentType, "content", 1); } - var type = new AutoPublishedContentType(0, "anything", CreatePropertyTypes); + var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; var umbracoContext = GetUmbracoContext("/test"); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 998fc92380..cafda161f4 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -83,8 +83,8 @@ namespace Umbraco.Tests.PublishedContent } var compositionAliases = new[] { "MyCompositionAlias" }; - var anythingType = new AutoPublishedContentType(0, "anything", compositionAliases, CreatePropertyTypes); - var homeType = new AutoPublishedContentType(0, "home", compositionAliases, CreatePropertyTypes); + var anythingType = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", compositionAliases, CreatePropertyTypes); + var homeType = new AutoPublishedContentType(Guid.NewGuid(), 0, "home", compositionAliases, CreatePropertyTypes); ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } @@ -398,8 +398,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_GroupBy_DocumentTypeAlias() { - var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { }); - var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { }); + var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); + var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); var contentTypes = new Dictionary { { home.Alias, home }, @@ -419,8 +419,8 @@ namespace Umbraco.Tests.PublishedContent [Test] public void Children_Where_DocumentTypeAlias() { - var home = new AutoPublishedContentType(22, "Home", new PublishedPropertyType[] { }); - var custom = new AutoPublishedContentType(23, "CustomDocument", new PublishedPropertyType[] { }); + var home = new AutoPublishedContentType(Guid.NewGuid(), 22, "Home", new PublishedPropertyType[] { }); + var custom = new AutoPublishedContentType(Guid.NewGuid(), 23, "CustomDocument", new PublishedPropertyType[] { }); var contentTypes = new Dictionary { { home.Alias, home }, @@ -903,7 +903,7 @@ namespace Umbraco.Tests.PublishedContent yield return factory.CreatePropertyType(contentType, "detached", 1003); } - var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); + var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); var pt = ct.GetPropertyType("detached"); var prop = new PublishedElementPropertyBase(pt, null, false, PropertyCacheLevel.None, 5548); Assert.IsInstanceOf(prop.GetValue()); @@ -935,7 +935,7 @@ namespace Umbraco.Tests.PublishedContent var guid = Guid.NewGuid(); - var ct = factory.CreateContentType(0, "alias", CreatePropertyTypes); + var ct = factory.CreateContentType(Guid.NewGuid(), 0, "alias", CreatePropertyTypes); var c = new ImageWithLegendModel(ct, guid, new Dictionary { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index d8dbabb569..4a93fadbdf 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -66,7 +66,7 @@ namespace Umbraco.Tests.PublishedContent pc.Setup(content => content.Path).Returns("-1,1"); pc.Setup(content => content.Parent).Returns(() => null); pc.Setup(content => content.Properties).Returns(new Collection()); - pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); + pc.Setup(content => content.ContentType).Returns(new PublishedContentType(Guid.NewGuid(), 22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); return pc; } } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index a7b6d3d18a..4a0af69999 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.PublishedContent { } } - class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache + class SolidPublishedContentCache : PublishedCacheBase, IPublishedContentCache2, IPublishedMediaCache2 { private readonly Dictionary _content = new Dictionary(); @@ -150,6 +150,11 @@ namespace Umbraco.Tests.PublishedContent throw new NotImplementedException(); } + public override IPublishedContentType GetContentType(Guid key) + { + throw new NotImplementedException(); + } + public override IEnumerable GetByContentType(IPublishedContentType contentType) { throw new NotImplementedException(); @@ -378,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent #endregion } - class PublishedContentStrong1 : PublishedContentModel + internal class PublishedContentStrong1 : PublishedContentModel { public PublishedContentStrong1(IPublishedContent content) : base(content) @@ -387,7 +392,7 @@ namespace Umbraco.Tests.PublishedContent public int StrongValue => this.Value("strongValue"); } - class PublishedContentStrong1Sub : PublishedContentStrong1 + internal class PublishedContentStrong1Sub : PublishedContentStrong1 { public PublishedContentStrong1Sub(IPublishedContent content) : base(content) @@ -396,7 +401,7 @@ namespace Umbraco.Tests.PublishedContent public int AnotherValue => this.Value("anotherValue"); } - class PublishedContentStrong2 : PublishedContentModel + internal class PublishedContentStrong2 : PublishedContentModel { public PublishedContentStrong2(IPublishedContent content) : base(content) @@ -405,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent public int StrongValue => this.Value("strongValue"); } - class AutoPublishedContentType : PublishedContentType + internal class AutoPublishedContentType : PublishedContentType { private static readonly IPublishedPropertyType Default; @@ -418,20 +423,20 @@ namespace Umbraco.Tests.PublishedContent Default = factory.CreatePropertyType("*", 666); } - public AutoPublishedContentType(int id, string alias, IEnumerable propertyTypes) - : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable propertyTypes) + : base(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } - public AutoPublishedContentType(int id, string alias, Func> propertyTypes) - : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, Func> propertyTypes) + : base(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } - public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) - : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + : base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } - public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) - : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) + public AutoPublishedContentType(Guid key, int id, string alias, IEnumerable compositionAliases, Func> propertyTypes) + : base(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } public override IPublishedPropertyType GetPropertyType(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs index 2aa01916fb..25d3eda081 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,7 +28,7 @@ namespace Umbraco.Tests.Routing Mock.Of(), Mock.Of()), }; - _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); + _publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 0c1f89f430..18fe268a9a 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Routing Mock.Of(), Mock.Of()), }; - _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); + _publishedContentType = new PublishedContentType(Guid.NewGuid(), 0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 2f960d498d..3a1ff36a0a 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -139,7 +139,7 @@ namespace Umbraco.Tests.Routing property.SetSourceValue("en", enMediaUrl, true); property.SetSourceValue("da", daMediaUrl); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), new [] { umbracoFilePropertyType }, ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}}; var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Auto, "da"); @@ -150,7 +150,7 @@ namespace Umbraco.Tests.Routing { var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), new[] {umbracoFilePropertyType}, ContentVariation.Nothing); return new SolidPublishedContent(contentType) diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 135172460d..786eebea9f 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Routing frequest.TemplateModel = template; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var type = new AutoPublishedContentType(22, "CustomDocument", new PublishedPropertyType[] { }); + var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "CustomDocument", new PublishedPropertyType[] { }); ContentTypesCache.GetPublishedContentTypeByAlias = alias => type; var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of(), context => diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index 02aa95cd2e..8043e25661 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -158,7 +158,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); @@ -203,7 +203,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); @@ -257,7 +257,7 @@ namespace Umbraco.Tests.Routing var umbracoSettings = Current.Configs.Settings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new SolidPublishedContent(contentType) { Id = 1234 }; var publishedContentCache = new Mock(); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 553d1b97ba..041dabe7d2 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1197,7 +1197,7 @@ namespace Umbraco.Tests.Services Assert.IsFalse(content.HasIdentity); // content cannot publish values because they are invalid - var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService); + var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService, ServiceContext.TextService); var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties, CultureImpact.Invariant); Assert.IsFalse(isValid); Assert.IsNotEmpty(invalidProperties); diff --git a/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs index e1e19918ce..28bdf59373 100644 --- a/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs +++ b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Services var propEditors = new PropertyEditorCollection(new DataEditorCollection(new[] { dataEditor })); - validationService = new PropertyValidationService(propEditors, dataTypeService.Object); + validationService = new PropertyValidationService(propEditors, dataTypeService.Object, Mock.Of()); } [Test] diff --git a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs index bce9bd4155..6c40e2842d 100644 --- a/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlImageSourceParserTests.cs @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Templates { //setup a mock url provider which we'll use for testing - var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var media = new Mock(); media.Setup(x => x.ContentType).Returns(mediaType); var mediaUrlProvider = new Mock(); diff --git a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs index 7cd96a32ed..861a7e4db6 100644 --- a/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs +++ b/src/Umbraco.Tests/Templates/HtmlLocalLinkParserTests.cs @@ -54,12 +54,12 @@ namespace Umbraco.Tests.Templates contentUrlProvider .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/my-test-url")); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = new Mock(); publishedContent.Setup(x => x.Id).Returns(1234); publishedContent.Setup(x => x.ContentType).Returns(contentType); - var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var media = new Mock(); media.Setup(x => x.ContentType).Returns(mediaType); var mediaUrlProvider = new Mock(); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index f7e3744600..7d8cedc9c6 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -42,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers new DataType(new VoidEditor(Mock.Of())) { Id = 1 }); var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] { }); + var type = new AutoPublishedContentType(Guid.NewGuid(), 0, "anything", new PublishedPropertyType[] { }); ContentTypesCache.GetPublishedContentTypeByAlias = alias => GetPublishedContentTypeByAlias(alias) ?? type; } diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index c55467431d..b4cd4ab05e 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -41,12 +41,12 @@ namespace Umbraco.Tests.TestHelpers.Entities }; var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -87 }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "bodyText", Name = "Body Text", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.RichtextEditor }); var metaCollection = new PropertyTypeCollection(true); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88 }); - metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -89 }); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "keywords", Name = "Meta Keywords", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = Constants.DataTypes.Textbox }); + metaCollection.Add(new PropertyType("test", ValueStorageType.Ntext) { Alias = "description", Name = "Meta Description", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.DataTypes.Textarea }); contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); contentType.PropertyGroups.Add(new PropertyGroup(metaCollection) { Name = "Meta", SortOrder = 2 }); diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index ee91427a63..f53b0bfff0 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Testing.TestingTests var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, Enumerable.Empty(), umbracoContext.VariationContextAccessor); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); + var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = Mock.Of(); Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3c359cdde8..f803f1cd1d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -147,7 +147,9 @@ + + @@ -265,7 +267,7 @@ - + @@ -512,6 +514,7 @@ + diff --git a/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs b/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs new file mode 100644 index 0000000000..fbb2ce9c80 --- /dev/null +++ b/src/Umbraco.Tests/Web/Validation/ContentModelValidatorTests.cs @@ -0,0 +1,369 @@ +using Moq; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Services; +using Umbraco.Core.Logging; +using Umbraco.Web; +using Umbraco.Web.Editors.Filters; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Editors.Binders; +using Umbraco.Core; +using Umbraco.Tests.Testing; +using Umbraco.Core.Mapping; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Composing; +using System.Web.Http.ModelBinding; +using Umbraco.Web.PropertyEditors; +using System.ComponentModel.DataAnnotations; +using Umbraco.Tests.TestHelpers; +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Web.PropertyEditors.Validation; + +namespace Umbraco.Tests.Web.Validation +{ + [UmbracoTest(Mapper = true, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)] + [TestFixture] + public class ContentModelValidatorTests : UmbracoTestBase + { + private const int ComplexDataTypeId = 9999; + private const string ContentTypeAlias = "textPage"; + private ContentType _contentType; + + public override void SetUp() + { + base.SetUp(); + + _contentType = MockedContentTypes.CreateTextPageContentType(ContentTypeAlias); + // add complex editor + _contentType.AddPropertyType( + new PropertyType("complexTest", ValueStorageType.Ntext) { Alias = "complex", Name = "Complex", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = ComplexDataTypeId }, + "Content"); + + // make them all validate with a regex rule that will not pass + foreach (var prop in _contentType.PropertyTypes) + { + prop.ValidationRegExp = "^donotmatch$"; + prop.ValidationRegExpMessage = "Does not match!"; + } + } + + protected override void Compose() + { + base.Compose(); + + var complexEditorConfig = new NestedContentConfiguration + { + ContentTypes = new[] + { + new NestedContentConfiguration.ContentType { Alias = "feature" } + } + }; + var dataTypeService = new Mock(); + dataTypeService.Setup(x => x.GetDataType(It.IsAny())) + .Returns((int id) => id == ComplexDataTypeId + ? Mock.Of(x => x.Configuration == complexEditorConfig) + : Mock.Of()); + + var contentTypeService = new Mock(); + contentTypeService.Setup(x => x.GetAll(It.IsAny())) + .Returns(() => new List + { + _contentType + }); + + var textService = new Mock(); + textService.Setup(x => x.Localize("validation/invalidPattern", It.IsAny(), It.IsAny>())).Returns(() => "invalidPattern"); + textService.Setup(x => x.Localize("validation/invalidNull", It.IsAny(), It.IsAny>())).Returns("invalidNull"); + textService.Setup(x => x.Localize("validation/invalidEmpty", It.IsAny(), It.IsAny>())).Returns("invalidEmpty"); + + Composition.RegisterUnique(x => Mock.Of(x => x.GetDataType(It.IsAny()) == Mock.Of())); + Composition.RegisterUnique(x => dataTypeService.Object); + Composition.RegisterUnique(x => contentTypeService.Object); + Composition.RegisterUnique(x => textService.Object); + + Composition.WithCollectionBuilder() + .Add() + .Add(); + } + + [Test] + public void Test_Serializer() + { + var nestedLevel2 = new ComplexEditorValidationResult(); + var id1 = Guid.NewGuid(); + var addressInfoElementTypeResult = new ComplexEditorElementTypeValidationResult("addressInfo", id1); + var cityPropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("city"); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("City is invalid")); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("City cannot be empty")); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("City is not in Australia", new[] { "country" })); + cityPropertyTypeResult.AddValidationResult(new ValidationResult("Not a capital city", new[] { "capital" })); + addressInfoElementTypeResult.ValidationResults.Add(cityPropertyTypeResult); + nestedLevel2.ValidationResults.Add(addressInfoElementTypeResult); + + var nestedLevel1 = new ComplexEditorValidationResult(); + var id2 = Guid.NewGuid(); + var addressBookElementTypeResult = new ComplexEditorElementTypeValidationResult("addressBook", id2); + var addressesPropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("addresses"); + addressesPropertyTypeResult.AddValidationResult(new ValidationResult("Must have at least 3 addresses", new[] { "counter" })); + addressesPropertyTypeResult.AddValidationResult(nestedLevel2); // This is a nested result within the level 1 + addressBookElementTypeResult.ValidationResults.Add(addressesPropertyTypeResult); + var bookNamePropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("bookName"); + bookNamePropertyTypeResult.AddValidationResult(new ValidationResult("Invalid address book name", new[] { "book" })); + addressBookElementTypeResult.ValidationResults.Add(bookNamePropertyTypeResult); + nestedLevel1.ValidationResults.Add(addressBookElementTypeResult); + + var id3 = Guid.NewGuid(); + var addressBookElementTypeResult2 = new ComplexEditorElementTypeValidationResult("addressBook", id3); + var addressesPropertyTypeResult2 = new ComplexEditorPropertyTypeValidationResult("addresses"); + addressesPropertyTypeResult2.AddValidationResult(new ValidationResult("Must have at least 2 addresses", new[] { "counter" })); + addressBookElementTypeResult2.ValidationResults.Add(addressesPropertyTypeResult); + var bookNamePropertyTypeResult2 = new ComplexEditorPropertyTypeValidationResult("bookName"); + bookNamePropertyTypeResult2.AddValidationResult(new ValidationResult("Name is too long")); + addressBookElementTypeResult2.ValidationResults.Add(bookNamePropertyTypeResult2); + nestedLevel1.ValidationResults.Add(addressBookElementTypeResult2); + + // books is the outer most validation result and doesn't have it's own direct ValidationResult errors + var outerError = new ComplexEditorValidationResult(); + var id4 = Guid.NewGuid(); + var addressBookCollectionElementTypeResult = new ComplexEditorElementTypeValidationResult("addressBookCollection", id4); + var booksPropertyTypeResult= new ComplexEditorPropertyTypeValidationResult("books"); + booksPropertyTypeResult.AddValidationResult(nestedLevel1); // books is the outer most validation result + addressBookCollectionElementTypeResult.ValidationResults.Add(booksPropertyTypeResult); + outerError.ValidationResults.Add(addressBookCollectionElementTypeResult); + + var serialized = JsonConvert.SerializeObject(outerError, Formatting.Indented, new ValidationResultConverter()); + Console.WriteLine(serialized); + + var jsonError = JsonConvert.DeserializeObject(serialized); + + Assert.IsNotNull(jsonError.SelectToken("$[0]")); + Assert.AreEqual(id4.ToString(), jsonError.SelectToken("$[0].$id").Value()); + Assert.AreEqual("addressBookCollection", jsonError.SelectToken("$[0].$elementTypeAlias").Value()); + Assert.AreEqual(string.Empty, jsonError.SelectToken("$[0].ModelState['_Properties.books.invariant.null'][0]").Value()); + + var error0 = jsonError.SelectToken("$[0].books") as JArray; + Assert.IsNotNull(error0); + Assert.AreEqual(id2.ToString(), error0.SelectToken("$[0].$id").Value()); + Assert.AreEqual("addressBook", error0.SelectToken("$[0].$elementTypeAlias").Value()); + Assert.IsNotNull(error0.SelectToken("$[0].ModelState")); + Assert.AreEqual(string.Empty, error0.SelectToken("$[0].ModelState['_Properties.addresses.invariant.null'][0]").Value()); + var error1 = error0.SelectToken("$[0].ModelState['_Properties.addresses.invariant.null.counter']") as JArray; + Assert.IsNotNull(error1); + Assert.AreEqual(1, error1.Count); + var error2 = error0.SelectToken("$[0].ModelState['_Properties.bookName.invariant.null.book']") as JArray; + Assert.IsNotNull(error2); + Assert.AreEqual(1, error2.Count); + + Assert.AreEqual(id3.ToString(), error0.SelectToken("$[1].$id").Value()); + Assert.AreEqual("addressBook", error0.SelectToken("$[1].$elementTypeAlias").Value()); + Assert.IsNotNull(error0.SelectToken("$[1].ModelState")); + Assert.AreEqual(string.Empty, error0.SelectToken("$[1].ModelState['_Properties.addresses.invariant.null'][0]").Value()); + var error6 = error0.SelectToken("$[1].ModelState['_Properties.addresses.invariant.null.counter']") as JArray; + Assert.IsNotNull(error6); + Assert.AreEqual(1, error6.Count); + var error7 = error0.SelectToken("$[1].ModelState['_Properties.bookName.invariant.null']") as JArray; + Assert.IsNotNull(error7); + Assert.AreEqual(1, error7.Count); + + Assert.IsNotNull(error0.SelectToken("$[0].addresses")); + Assert.AreEqual(id1.ToString(), error0.SelectToken("$[0].addresses[0].$id").Value()); + Assert.AreEqual("addressInfo", error0.SelectToken("$[0].addresses[0].$elementTypeAlias").Value()); + Assert.IsNotNull(error0.SelectToken("$[0].addresses[0].ModelState")); + var error3 = error0.SelectToken("$[0].addresses[0].ModelState['_Properties.city.invariant.null.country']") as JArray; + Assert.IsNotNull(error3); + Assert.AreEqual(1, error3.Count); + var error4 = error0.SelectToken("$[0].addresses[0].ModelState['_Properties.city.invariant.null.capital']") as JArray; + Assert.IsNotNull(error4); + Assert.AreEqual(1, error4.Count); + var error5 = error0.SelectToken("$[0].addresses[0].ModelState['_Properties.city.invariant.null']") as JArray; + Assert.IsNotNull(error5); + Assert.AreEqual(2, error5.Count); + } + + [Test] + public void Validating_ContentItemSave() + { + var validator = new ContentSaveModelValidator( + Factory.GetInstance(), + Mock.Of(), + Factory.GetInstance()); + + var content = MockedContent.CreateTextpageContent(_contentType, "test", -1); + + var id1 = new Guid("c8df5136-d606-41f0-9134-dea6ae0c2fd9"); + var id2 = new Guid("f916104a-4082-48b2-a515-5c4bf2230f38"); + var id3 = new Guid("77E15DE9-1C79-47B2-BC60-4913BC4D4C6A"); + + // TODO: Ok now test with a 4th level complex nested editor + + var complexValue = @"[{ + ""key"": """ + id1.ToString() + @""", + ""name"": ""Hello world"", + ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", + ""title"": ""Hello world"", + ""bodyText"": ""The world is round"" + }, { + ""key"": """ + id2.ToString() + @""", + ""name"": ""Super nested"", + ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", + ""title"": ""Hi there!"", + ""bodyText"": ""Well hello there"", + ""complex"" : [{ + ""key"": """ + id3.ToString() + @""", + ""name"": ""I am a sub nested content"", + ""ncContentTypeAlias"": """ + ContentTypeAlias + @""", + ""title"": ""Hello up there :)"", + ""bodyText"": ""Hello way up there on a different level"" + }] + } + ]"; + content.SetValue("complex", complexValue); + + // map the persisted properties to a model representing properties to save + //var saveProperties = content.Properties.Select(x => Mapper.Map(x)).ToList(); + var saveProperties = content.Properties.Select(x => + { + return new ContentPropertyBasic + { + Alias = x.Alias, + Id = x.Id, + Value = x.GetValue() + }; + }).ToList(); + + var saveVariants = new List + { + new ContentVariantSave + { + Culture = string.Empty, + Segment = string.Empty, + Name = content.Name, + Save = true, + Properties = saveProperties + } + }; + + var save = new ContentItemSave + { + Id = content.Id, + Action = ContentSaveAction.Save, + ContentTypeAlias = _contentType.Alias, + ParentId = -1, + PersistedContent = content, + TemplateAlias = null, + Variants = saveVariants + }; + + // This will map the ContentItemSave.Variants.PropertyCollectionDto and then map the values in the saved model + // back onto the persisted IContent model. + ContentItemBinder.BindModel(save, content); + + var modelState = new ModelStateDictionary(); + var isValid = validator.ValidatePropertiesData(save, saveVariants[0], saveVariants[0].PropertyCollectionDto, modelState); + + // list results for debugging + foreach (var state in modelState) + { + Console.WriteLine(state.Key); + foreach (var error in state.Value.Errors) + { + Console.WriteLine("\t" + error.ErrorMessage); + } + } + + // assert + + Assert.IsFalse(isValid); + Assert.AreEqual(11, modelState.Keys.Count); + const string complexPropertyKey = "_Properties.complex.invariant.null"; + Assert.IsTrue(modelState.Keys.Contains(complexPropertyKey)); + foreach (var state in modelState.Where(x => x.Key != complexPropertyKey)) + { + foreach (var error in state.Value.Errors) + { + Assert.IsFalse(error.ErrorMessage.DetectIsJson()); // non complex is just an error message + } + } + var complexEditorErrors = modelState.Single(x => x.Key == complexPropertyKey).Value.Errors; + Assert.AreEqual(1, complexEditorErrors.Count); + var nestedError = complexEditorErrors[0]; + var jsonError = JsonConvert.DeserializeObject(nestedError.ErrorMessage); + + var modelStateKeys = new[] { "_Properties.title.invariant.null.innerFieldId", "_Properties.title.invariant.null.value", "_Properties.bodyText.invariant.null.innerFieldId", "_Properties.bodyText.invariant.null.value" }; + AssertNestedValidation(jsonError, 0, id1, modelStateKeys); + AssertNestedValidation(jsonError, 1, id2, modelStateKeys.Concat(new[] { "_Properties.complex.invariant.null.innerFieldId", "_Properties.complex.invariant.null.value" }).ToArray()); + var nestedJsonError = jsonError.SelectToken("$[1].complex") as JArray; + Assert.IsNotNull(nestedJsonError); + AssertNestedValidation(nestedJsonError, 0, id3, modelStateKeys); + } + + private void AssertNestedValidation(JArray jsonError, int index, Guid id, string[] modelStateKeys) + { + Assert.IsNotNull(jsonError.SelectToken("$[" + index + "]")); + Assert.AreEqual(id.ToString(), jsonError.SelectToken("$[" + index + "].$id").Value()); + Assert.AreEqual("textPage", jsonError.SelectToken("$[" + index + "].$elementTypeAlias").Value()); + Assert.IsNotNull(jsonError.SelectToken("$[" + index + "].ModelState")); + foreach (var key in modelStateKeys) + { + var error = jsonError.SelectToken("$[" + index + "].ModelState['" + key + "']") as JArray; + Assert.IsNotNull(error); + Assert.AreEqual(1, error.Count); + } + } + + [HideFromTypeFinder] + [DataEditor("complexTest", "test", "test")] + public class ComplexTestEditor : NestedContentPropertyEditor + { + public ComplexTestEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) : base(logger, propertyEditors, dataTypeService, contentTypeService) + { + } + + protected override IDataValueEditor CreateValueEditor() + { + var editor = base.CreateValueEditor(); + editor.Validators.Add(new NeverValidateValidator()); + return editor; + } + } + + [HideFromTypeFinder] + [DataEditor("test", "test", "test")] // This alias aligns with the prop editor alias for all properties created from MockedContentTypes.CreateTextPageContentType + public class TestEditor : DataEditor + { + public TestEditor(ILogger logger) + : base(logger) + { + } + + protected override IDataValueEditor CreateValueEditor() => new TestValueEditor(Attribute); + + private class TestValueEditor : DataValueEditor + { + public TestValueEditor(DataEditorAttribute attribute) + : base(attribute) + { + Validators.Add(new NeverValidateValidator()); + } + + } + } + + public class NeverValidateValidator : IValueValidator + { + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + yield return new ValidationResult("WRONG!", new[] { "innerFieldId" }); + } + } + + } +} diff --git a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs b/src/Umbraco.Tests/Web/Validation/ModelStateExtensionsTests.cs similarity index 99% rename from src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs rename to src/Umbraco.Tests/Web/Validation/ModelStateExtensionsTests.cs index 7b25e60b5a..0355705378 100644 --- a/src/Umbraco.Tests/Web/ModelStateExtensionsTests.cs +++ b/src/Umbraco.Tests/Web/Validation/ModelStateExtensionsTests.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using Umbraco.Core.Services; using Umbraco.Web; -namespace Umbraco.Tests.Web +namespace Umbraco.Tests.Web.Validation { [TestFixture] public class ModelStateExtensionsTests diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 542d45c479..d272c77397 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -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(setTestMode, parallel(js, testUnit)); -exports.runUnit = series(setTestMode, parallel(js, runUnitTestServer), watchTask); +exports.runTests = series(setTestMode, series(js, testUnit)); +exports.runUnit = series(setTestMode, series(js, runUnitTestServer), watchTask); exports.testE2e = series(setTestMode, parallel(testE2e)); diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js index 823d3d526d..54fda13a0d 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js @@ -69,6 +69,18 @@ }; } + if (!String.prototype.trimStartSpecial) { + /** trimSpecial extension method for string */ + // Removes all non printable chars from beginning of a string + String.prototype.trimStartSpecial = function () { + var index = 0; + while (this.charCodeAt(index) <= 46) { + index++; + } + return this.substr(index); + }; + } + if (!String.prototype.startsWith) { /** startsWith extension method for string */ String.prototype.startsWith = function (str) { diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json new file mode 100644 index 0000000000..afe9f05200 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -0,0 +1,16253 @@ +{ + "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.8.0", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.0.tgz", + "integrity": "sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg==" + }, + "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": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "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": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "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": "https://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": "http://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": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "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": "http://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.30001107", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz", + "integrity": "sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ==", + "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.9.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", + "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", + "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": "http://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": "http://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": "http://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": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "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": "http://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": "http://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": "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 + } + } + }, + "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": "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 + } + } + }, + "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": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "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": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", + "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": "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "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.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", + "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.4.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.2.0", + "yargs": "^7.1.0" + } + }, + "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": "http://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": "http://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": "http://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": "http://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": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "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.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "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": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "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": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "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": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "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": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "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.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" + }, + "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": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "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-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "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": "http://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": "http://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" + } + }, + "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" + } + }, + "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": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "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" + }, + "dependencies": { + "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" + } + } + } + }, + "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": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "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.4.0", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.4.0.tgz", + "integrity": "sha512-D1aYsT73yWrSNcRfqcovE//htpfFqQwd+m+9UCIVSsRupwD7kodSj6j/DTJur5mqnv5HckSJvjHekyVZKLi6dA==" + }, + "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.14.7", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.7.tgz", + "integrity": "sha512-swhsdpNpyXg4GbM6LpOQ6qaloQuIKizZ+Zh6JPXJQc59ka49100Js0WvZx594iaKSoFgkFq2s8uXFHS3/Xy2WQ==", + "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.8", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.3", + "call-limit": "^1.1.1", + "chownr": "^1.1.4", + "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.1", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.8", + "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.8", + "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.4", + "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.5", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.1.0", + "nopt": "^4.0.3", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.3", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "^3.0.2", + "npm-lifecycle": "^3.1.5", + "npm-package-arg": "^6.1.1", + "npm-packlist": "^1.4.8", + "npm-pick-manifest": "^3.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.5", + "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.6.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.7.1", + "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.8", + "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.4", + "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": "5.0.0", + "bundled": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", + "bundled": 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", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.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.6.0", + "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 + }, + "emoji-regex": { + "version": "7.0.3", + "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": "2.2.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 + }, + "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.1", + "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": "2.0.5", + "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.6", + "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.4", + "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.8", + "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" + } + }, + "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.2.1", + "bundled": true, + "requires": { + "ci-info": "^1.5.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.2.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 + }, + "libcipm": { + "version": "4.0.8", + "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.1.0", + "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.4", + "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": "^14.2.3" + } + }, + "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 + }, + "mime-db": { + "version": "1.35.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "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.5", + "bundled": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true + } + } + }, + "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.1.0", + "bundled": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, + "nopt": { + "version": "4.0.3", + "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.3", + "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.5", + "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.8", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^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.4", + "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.5", + "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.1", + "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-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 + }, + "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.8", + "bundled": 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.5", + "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.6.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.4.0", + "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": "2.0.0", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.7.1", + "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.5", + "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.3.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } + } + }, + "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.1", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", + "bundled": 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", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.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": "14.2.3", + "bundled": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.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 + }, + "string-width": { + "version": "3.1.0", + "bundled": 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", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "15.0.1", + "bundled": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "bundled": true + } + } + } + } + }, + "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": "http://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": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "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": "http://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": "http://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": "http://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": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "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": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "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": "http://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": "http://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": "http://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": "http://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.10", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.10.tgz", + "integrity": "sha512-vyzGG04Q44Y7zWIKA4c+G7MxMCsed6JkrhU+k0TaDs9XKAiS+e+D3Fzz5OIJ7p5keF7lbRK5czgI8T1JtouZqw==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "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": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "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": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "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 + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "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": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "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.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "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-security.0" + } + }, + "yargs-parser": { + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.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 01bcb42d8b..100774f385 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -2,7 +2,7 @@ "private": true, "scripts": { "test": "gulp runTests", - "unit": "gulp testUnit", + "unit": "gulp runUnit", "e2e": "gulp testE2e", "build": "gulp build", "dev": "gulp dev", @@ -73,6 +73,7 @@ "gulp-wrap-js": "0.4.1", "jasmine-core": "3.5.0", "karma": "4.4.1", + "karma-chrome-launcher": "^3.1.0", "karma-jasmine": "2.0.1", "karma-junit-reporter": "2.0.1", "karma-phantomjs-launcher": "1.0.4", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js index 92869f39bd..b34a9eb8fd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour.directive.js @@ -5,14 +5,14 @@ @scope @description -Added in Umbraco 7.8. The tour component is a global component and is already added to the umbraco markup. -In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco. -You can easily add you own tours to the Help-drawer or show and start tours from +Added in Umbraco 7.8. The tour component is a global component and is already added to the umbraco markup. +In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco. +You can easily add you own tours to the Help-drawer or show and start tours from anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install The Starter Kit in Umbraco 7.8

Extending the help drawer with custom tours

-The easiest way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. -Place the file in App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json and it will automatically be +The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file. +Place the file in App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json and it will automatically be picked up by Umbraco and shown in the Help-drawer.

The tour object

@@ -26,7 +26,7 @@ The tour object consist of two parts - The overall tour configuration and a list "groupOrder": 200 // Control the order of tour groups "allowDisable": // Adds a "Don't" show this tour again"-button to the intro step "culture" : // From v7.11+. Specifies the culture of the tour (eg. en-US), if set the tour will only be shown to users with this culture set on their profile. If omitted or left empty the tour will be visible to all users - "requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load. + "requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load. "steps": [] // tour steps - see next example } @@ -43,11 +43,12 @@ The tour object consist of two parts - The overall tour configuration and a list "backdropOpacity": 0.4 // the backdrop opacity "view": "" // add a custom view "customProperties" : {} // add any custom properties needed for the custom view + "skipStepIfVisible": ".dashboard div [data-element='my-tour-button']" // if we can find this DOM element on the page then we will skip this step }

Adding tours to other parts of the Umbraco backoffice

-It is also possible to add a list of custom tours to other parts of the Umbraco backoffice, +It is also possible to add a list of custom tours to other parts of the Umbraco backoffice, as an example on a Dashboard in a Custom section. You can then use the {@link umbraco.services.tourService tourService} to start and stop tours but you don't have to register them as part of the tour service.

Using the tour service

@@ -86,7 +87,8 @@ as an example on a Dashboard in a Custom section. You can then use the {@link um "element": "[data-element='my-tour-button']", "title": "Click the button", "content": "Click the button", - "event": "click" + "event": "click", + "skipStepIfVisible": "[data-element='my-other-tour-button']" } ] }; @@ -257,9 +259,26 @@ In the following example you see how to run some custom logic before a step goes // make sure we don't go too far if (scope.model.currentStepIndex !== scope.model.steps.length) { + + var upcomingStep = scope.model.steps[scope.model.currentStepIndex]; + + // If the currentStep JSON object has 'skipStepIfVisible' + // It's a DOM selector - if we find it then we ship over this step + if (upcomingStep.skipStepIfVisible) { + let tryFindDomEl = document.querySelector(upcomingStep.skipStepIfVisible); + if (tryFindDomEl) { + // check if element is visible: + if( tryFindDomEl.offsetWidth || tryFindDomEl.offsetHeight || tryFindDomEl.getClientRects().length ) { + // if it was visible then we skip the step. + nextStep(); + return; + } + } + } + startStep(); - // tour completed - final step } else { + // tour completed - final step scope.loadingStep = true; waitForPendingRerequests().then(function () { 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 4f3e02a8c9..937c7467e4 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 @@ -4,7 +4,8 @@ function ContentEditController($rootScope, $scope, $routeParams, $q, $window, appState, contentResource, entityResource, navigationService, notificationsService, serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper, - editorState, $http, eventsService, overlayService, $location, localStorageService, treeService) { + editorState, $http, eventsService, overlayService, $location, localStorageService, treeService, + $exceptionHandler) { var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; @@ -521,6 +522,12 @@ } } + function handleHttpException(err) { + if (err && !err.status) { + $exceptionHandler(err); + } + } + /** Just shows a simple notification that there are client side validation issues to be fixed */ function showValidationNotification() { //TODO: We need to make the validation UI much better, there's a lot of inconsistencies in v8 including colors, issues with the property groups and validation errors between variants @@ -561,6 +568,7 @@ view: "views/content/overlays/unpublish.html", variants: $scope.content.variants, //set a model property for the dialog skipFormValidation: true, //when submitting the overlay form, skip any client side validation + includeUnpublished: false, submitButtonLabelKey: "content_unpublish", submitButtonStyle: "warning", submit: function (model) { @@ -581,6 +589,7 @@ overlayService.close(); }, function (err) { $scope.page.buttonGroupState = 'error'; + handleHttpException(err); }); @@ -626,8 +635,7 @@ model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); + handleHttpException(err); }); }, close: function () { @@ -648,8 +656,9 @@ action: "sendToPublish" }).then(function () { $scope.page.buttonGroupState = "success"; - }, function () { + }, function (err) { $scope.page.buttonGroupState = "error"; + handleHttpException(err); });; } }; @@ -685,8 +694,7 @@ model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); + handleHttpException(err); }); }, close: function () { @@ -709,8 +717,9 @@ action: "publish" }).then(function () { $scope.page.buttonGroupState = "success"; - }, function () { + }, function (err) { $scope.page.buttonGroupState = "error"; + handleHttpException(err); }); } }; @@ -748,8 +757,7 @@ model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); + handleHttpException(err); }); }, close: function (oldModel) { @@ -772,8 +780,9 @@ action: "save" }).then(function () { $scope.page.saveButtonState = "success"; - }, function () { + }, function (err) { $scope.page.saveButtonState = "error"; + handleHttpException(err); }); } @@ -826,8 +835,7 @@ model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties dialog.variants = Utilities.copy($scope.content.variants); - //don't reject, we've handled the error - return $q.when(err); + handleHttpException(err); }); }, @@ -886,8 +894,7 @@ model.submitButtonState = "error"; //re-map the dialog model since we've re-bound the properties dialog.variants = $scope.content.variants; - //don't reject, we've handled the error - return $q.when(err); + handleHttpException(err); }); }, 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 4fbc18abd0..c3fd0dc9c4 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 @@ -38,6 +38,8 @@ vm.selectAppAnchor = selectAppAnchor; vm.requestSplitView = requestSplitView; + vm.getScope = getScope;// used by property editors to get a scope that is the root of split view, content apps etc. + //Used to track how many content views there are (for split view there will be 2, it could support more in theory) vm.editors = []; @@ -244,6 +246,10 @@ vm.onSelectAppAnchor({"app": app, "anchor": anchor}); } } + function getScope() { + return $scope; + } + } angular.module('umbraco.directives').component('umbVariantContentEditors', umbVariantContentEditors); 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 9b8f92d7c3..fdec4c98d0 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,10 +2,10 @@ 'use strict'; function EditorContentHeader(serverValidationManager, localizationService, editorState) { - function link(scope) { + function link(scope) { var unsubscribe = []; - + if (!scope.serverValidationNameField) { scope.serverValidationNameField = "Name"; } @@ -46,55 +46,55 @@ 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) { + scope.content.variants.forEach(function (variant) { variant.hasError = scope.variantHasError(variant); - + }); checkErrorsOnOtherVariants(); } function checkErrorsOnOtherVariants() { var check = false; - scope.content.variants.forEach( function (variant) { + 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) { // only want to react to property errors: - if(errors.findIndex(error => {return error.propertyAlias !== null;}) === -1) { + 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") { + if (culture === "invariant" && scope.vm.defaultVariant) { culture = scope.vm.defaultVariant.language.culture; } var index = scope.vm.variantsWithError.findIndex((item) => item.culture === culture && item.segment === segment) - if(valid === true) { + if (valid === true) { if (index !== -1) { scope.vm.variantsWithError.splice(index, 1); } } else { if (index === -1) { - scope.vm.variantsWithError.push({"culture": culture, "segment": segment}); + scope.vm.variantsWithError.push({ "culture": culture, "segment": segment }); } } scope.$evalAsync(updateVaraintErrors); } - + function onInit() { - + // find default + check if we have variants. - scope.content.variants.forEach( function (variant) { + scope.content.variants.forEach(function (variant) { if (variant.language !== null && variant.language.isDefault) { scope.vm.defaultVariant = variant; } @@ -113,41 +113,41 @@ scope.vm.variantMenu = []; if (scope.vm.hasCulture) { - scope.content.variants.forEach( (v) => { + 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) + 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.content.variants.forEach((v) => { scope.vm.variantMenu.push({ key: String.CreateGuid(), variant: v }); }); } - - scope.editor.variantApps.forEach( (app) => { + + scope.editor.variantApps.forEach((app) => { if (app.alias === "umbContent") { app.anchors = scope.editor.content.tabs; } }); - scope.content.variants.forEach( function (variant) { - + 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) { + if (variant.language && scope.vm.defaultVariant && 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.goBack = function () { @@ -164,15 +164,15 @@ } }; - 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 }); } } @@ -188,20 +188,20 @@ 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) { + 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; } onInit(); - + scope.$on('$destroy', function () { for (var u in unsubscribe) { unsubscribe[u](); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js index 672b3fa286..20fba6eb6e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js @@ -11,13 +11,13 @@ var sectionId = '#leftcolumn'; var isLeftColumnAbove = false; scope.editors = []; - + function addEditor(editor) { editor.inFront = true; editor.moveRight = true; editor.level = 0; editor.styleIndex = 0; - + // push the new editor to the dom scope.editors.push(editor); @@ -32,20 +32,20 @@ $timeout(() => { editor.moveRight = false; }) - + editor.animating = true; setTimeout(revealEditorContent.bind(this, editor), 400); - + updateEditors(); } - + function removeEditor(editor) { editor.moveRight = true; - + editor.animating = true; setTimeout(removeEditorFromDOM.bind(this, editor), 400); - + updateEditors(-1); if(scope.editors.length === 1){ @@ -56,17 +56,17 @@ isLeftColumnAbove = false; } } - + function revealEditorContent(editor) { - + editor.animating = false; - + scope.$digest(); - + } - + function removeEditorFromDOM(editor) { - + // push the new editor to the dom var index = scope.editors.indexOf(editor); if (index !== -1) { @@ -74,42 +74,42 @@ } updateEditors(); - + scope.$digest(); - + } - + /** update layer positions. With ability to offset positions, needed for when an item is moving out, then we dont want it to influence positions */ function updateEditors(offset) { - + offset = offset || 0;// fallback value. - + var len = scope.editors.length; var calcLen = len + offset; var ceiling = Math.min(calcLen, allowedNumberOfVisibleEditors); - var origin = Math.max(calcLen-1, 0)-ceiling; + var origin = Math.max(calcLen - 1, 0) - ceiling; var i = 0; - while(i= ceiling; i++; } } - + evts.push(eventsService.on("appState.editors.open", function (name, args) { addEditor(args.editor); })); evts.push(eventsService.on("appState.editors.close", function (name, args) { // remove the closed editor - if(args && args.editor) { + if (args && args.editor) { removeEditor(args.editor); } // close all editors - if(args && !args.editor && args.editors.length === 0) { + if (args && !args.editor && args.editors.length === 0) { scope.editors = []; } })); @@ -134,6 +134,74 @@ } + // This directive allows for us to run a custom $compile for the view within the repeater which allows + // us to maintain a $scope hierarchy with the rendered view based on the $scope that initiated the + // infinite editing. The retain the $scope hiearchy a special $parentScope property is passed in to the model. + function EditorRepeaterDirective($http, $templateCache, $compile, angularHelper) { + function link(scope, el, attr, ctrl) { + + var editor = scope && scope.$parent ? scope.$parent.model : null; + if (!editor) { + return; + } + + var unsubscribe = []; + + //if a custom parent scope is defined then we need to manually compile the view + if (editor.$parentScope) { + var element = el.find(".scoped-view"); + $http.get(editor.view, { cache: $templateCache }) + .then(function (response) { + var templateScope = editor.$parentScope.$new(); + + unsubscribe.push(function () { + templateScope.$destroy(); + }); + + // NOTE: the 'model' name here directly affects the naming convention used in infinite editors, this why you access the model + // like $scope.model.If this is changed, everything breaks.This is because we are entirely reliant upon ng-include and inheriting $scopes. + // by default without a $parentScope used for infinite editing the 'model' propety will be set because the view creates the scopes in + // ng-repeat by ng-repeat="model in editors" + templateScope.model = editor; + + element.show(); + + // if a parentForm is supplied then we can link them but to do that we need to inject a top level form + if (editor.$parentForm) { + element.html("" + response.data + ""); + } + + $compile(element)(templateScope); + + // if a parentForm is supplied then we can link them + if (editor.$parentForm) { + editor.$parentForm.$addControl(templateScope.infiniteEditorForm); + } + }); + } + + scope.$on('$destroy', function () { + for (var i = 0; i < unsubscribe.length; i++) { + unsubscribe[i](); + } + }); + } + + var directive = { + restrict: 'E', + replace: true, + transclude: true, + scope: { + editors: "=" + }, + template: "
", + link: link + }; + + return directive; + } + angular.module('umbraco.directives').directive('umbEditors', EditorsDirective); + angular.module('umbraco.directives').directive('umbEditorRepeater', EditorRepeaterDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js index d8dbcc1012..dda5c51175 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/focuswhen.directive.js @@ -2,13 +2,20 @@ angular.module("umbraco.directives").directive('focusWhen', function ($timeout) return { restrict: 'A', link: function (scope, elm, attrs, ctrl) { + + var delayTimer; + attrs.$observe("focusWhen", function (newValue) { - if (newValue === "true") { - $timeout(function () { - elm.trigger("focus"); - }); + if (newValue === "true" && document.activeelement !== elm[0]) { + delayTimer = $timeout(function () { + elm[0].focus(); + }); } }); + + scope.$on('$destroy', function() { + $timeout.cancel(delayTimer); + }); } }; }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js index c85b37a8a4..f7ba043ef1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js @@ -284,7 +284,7 @@ Opens an overlay to show a custom YSOD.
templateScope.model = scope.model; element.html(response.data); element.show(); - $compile(element.contents())(templateScope); + $compile(element)(templateScope); }); } } @@ -473,7 +473,7 @@ Opens an overlay to show a custom YSOD.
scope.submitForm = function (model) { if (scope.model.submit) { - if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation })) { + if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation, keepServerValidation: true })) { if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) { //wrap in a when since we don't know if this is a promise or not 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 ad62bcd3db..2b2f36dd7d 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 @@ -3,46 +3,75 @@ * @name umbraco.directives.directive:umbProperty * @restrict E **/ -angular.module("umbraco.directives") - .directive('umbProperty', function (userService) { - return { - scope: { +(function () { + 'use strict'; + + angular + .module("umbraco.directives") + .component('umbProperty', { + templateUrl: 'views/components/property/umb-property.html', + controller: UmbPropertyController, + controllerAs: 'vm', + transclude: true, + require: { + parentUmbProperty: '?^^umbProperty', + parentForm: '?^^form' + }, + bindings: { property: "=", + elementKey: "@", + // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) + propertyAlias: "@", showInherit: "<", inheritsFrom: "<" - }, - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/property/umb-property.html', - link: function (scope) { - - 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) { - - var self = this; - - //set the API properties/methods - - self.property = $scope.property; - self.setPropertyError = function (errorMsg) { - $scope.property.propertyErrorMessage = errorMsg; - }; - - $scope.propertyActions = []; - self.setPropertyActions = function(actions) { - $scope.propertyActions = actions; - }; - } + }); + + + + function UmbPropertyController($scope, userService, serverValidationManager, udiService, angularHelper) { + + const vm = this; + + vm.$onInit = onInit; + + vm.setPropertyError = function (errorMsg) { + vm.property.propertyErrorMessage = errorMsg; }; - }); + + vm.propertyActions = []; + vm.setPropertyActions = function (actions) { + vm.propertyActions = actions; + }; + + // returns the validation path for the property to be used as the validation key for server side validation logic + vm.getValidationPath = function () { + + var parentValidationPath = vm.parentUmbProperty ? vm.parentUmbProperty.getValidationPath() : null; + var propAlias = vm.propertyAlias ? vm.propertyAlias : vm.property.alias; + // the elementKey will be empty when this is not a nested property + var valPath = vm.elementKey ? vm.elementKey + "/" + propAlias : propAlias; + return serverValidationManager.createPropertyValidationKey(valPath, parentValidationPath); + } + + function onInit() { + vm.controlLabelTitle = null; + if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if (u.allowedSections.indexOf("settings") !== -1 ? true : false) { + vm.controlLabelTitle = vm.property.alias; + } + }); + } + + if (!vm.parentUmbProperty) { + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain($scope, s => s && s.vm && s.vm.constructor.name === "UmbPropertyController"); + vm.parentUmbProperty = found ? found.vm : null; + } + } + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js index bc74cbb13b..b006f75e6b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchresults.directive.js @@ -9,13 +9,14 @@ function treeSearchResults() { return { scope: { results: "=", - selectResultCallback: "=" + selectResultCallback: "=", + emptySearchResultPosition: '@' }, restrict: "E", // restrict to an element replace: true, // replace the html element with the template templateUrl: 'views/components/tree/umb-tree-search-results.html', link: function (scope, element, attrs, ctrl) { - + scope.emptySearchResultPosition = scope.emptySearchResultPosition || "center"; } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js index 72dba3ca2f..f9b26c81a5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbnestedcontent.directive.js @@ -49,6 +49,7 @@ } }); + } }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtextarea.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtextarea.directive.js new file mode 100644 index 0000000000..8b584b873c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtextarea.directive.js @@ -0,0 +1,59 @@ +(function () { + 'use strict'; + + function umbTextarea($document) { + + function autogrow(scope, element, attributes) { + if (!element.hasClass("autogrow")) { + // no autogrow for you today + return; + } + + // get possible minimum height style + var minHeight = parseInt(window.getComputedStyle(element[0]).getPropertyValue("min-height")) || 0; + + // prevent newlines in textbox + element.on("keydown", function (evt) { + if (evt.which === 13) { + //evt.preventDefault(); + } + }); + + element.on("input", function (evt) { + element.css({ + height: 'auto', + minHeight: 0 + }); + + var contentHeight = this.scrollHeight; + var borderHeight = 1; + var paddingHeight = 4; + + element.css({ + minHeight: null, // remove property + height: contentHeight + borderHeight + paddingHeight + "px" // because we're using border-box + }); + }); + + // watch model changes from the outside to adjust height + scope.$watch(attributes.ngModel, trigger); + + // set initial size + trigger(); + + function trigger() { + setTimeout(element.triggerHandler.bind(element, "input"), 1); + } + } + + var directive = { + restrict: 'E', + link: autogrow + }; + + return directive; + } + + angular.module('umbraco.directives').directive('textarea', umbTextarea); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index a6bf783590..e42ac1d94d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -12,48 +12,88 @@ * Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will * be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly. **/ -function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService) { +function valFormManager(serverValidationManager, $rootScope, $timeout, $location, overlayService, eventsService, $routeParams, navigationService, editorService, localizationService, angularHelper) { var SHOW_VALIDATION_CLASS_NAME = "show-validation"; var SAVING_EVENT_NAME = "formSubmitting"; var SAVED_EVENT_NAME = "formSubmitted"; + function notify(scope) { + scope.$broadcast("valStatusChanged", { form: scope.formCtrl }); + } + + function ValFormManagerController($scope) { + //This exposes an API for direct use with this directive + + // We need this as a way to reference this directive in the scope chain. Since this directive isn't a component and + // because it's an attribute instead of an element, we can't use controllerAs or anything like that. Plus since this is + // an attribute an isolated scope doesn't work so it's a bit weird. By doing this we are able to lookup the parent valFormManager + // in the scope hierarchy even if the DOM hierarchy doesn't match (i.e. in infinite editing) + $scope.valFormManager = this; + + var unsubscribe = []; + var self = this; + + //This is basically the same as a directive subscribing to an event but maybe a little + // nicer since the other directive can use this directive's API instead of a magical event + this.onValidationStatusChanged = function (cb) { + unsubscribe.push($scope.$on("valStatusChanged", function (evt, args) { + cb.apply(self, [evt, args]); + })); + }; + + this.isShowingValidation = () => $scope.showValidation === true; + + this.notify = function () { + notify($scope); + } + + this.isValid = function () { + return !$scope.formCtrl.$invalid; + } + + //Ensure to remove the event handlers when this instance is destroyted + $scope.$on('$destroy', function () { + for (var u in unsubscribe) { + unsubscribe[u](); + } + }); + } + + /** + * Find's the valFormManager in the scope/DOM hierarchy + * @param {any} scope + * @param {any} ctrls + * @param {any} index + */ + function getAncestorValFormManager(scope, ctrls, index) { + + // first check the normal directive inheritance which relies on DOM inheritance + var found = ctrls[index]; + if (found) { + return found; + } + + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain(scope, s => s && s.valFormManager && s.valFormManager.constructor.name === "ValFormManagerController"); + return found ? found.valFormManager : null; + } + return { require: ["form", "^^?valFormManager", "^^?valSubView"], restrict: "A", - controller: function($scope) { - //This exposes an API for direct use with this directive - - var unsubscribe = []; - var self = this; - - //This is basically the same as a directive subscribing to an event but maybe a little - // nicer since the other directive can use this directive's API instead of a magical event - this.onValidationStatusChanged = function (cb) { - unsubscribe.push($scope.$on("valStatusChanged", function(evt, args) { - cb.apply(self, [evt, args]); - })); - }; - - this.showValidation = $scope.showValidation === true; - - //Ensure to remove the event handlers when this instance is destroyted - $scope.$on('$destroy', function () { - for (var u in unsubscribe) { - unsubscribe[u](); - } - }); - }, + controller: ValFormManagerController, link: function (scope, element, attr, ctrls) { function notifySubView() { - if (subView){ + if (subView) { subView.valStatusChanged({ form: formCtrl, showValidation: scope.showValidation }); } } - var formCtrl = ctrls[0]; - var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null; + var formCtrl = scope.formCtrl = ctrls[0]; + var parentFormMgr = scope.parentFormMgr = getAncestorValFormManager(scope, ctrls, 1); var subView = ctrls.length > 1 ? ctrls[2] : null; var labels = {}; @@ -72,45 +112,22 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location }); //watch the list of validation errors to notify the application of any validation changes - scope.$watch(function () { - //the validators are in the $error collection: https://docs.angularjs.org/api/ng/type/form.FormController#$error - //since each key is the validator name (i.e. 'required') we can't just watch the number of keys, we need to watch - //the sum of the items inside of each key + scope.$watch(() => angularHelper.countAllFormErrors(formCtrl), + function (e) { + + notify(scope); + + notifySubView(); + + //find all invalid elements' .control-group's and apply the error class + var inError = element.find(".control-group .ng-invalid").closest(".control-group"); + inError.addClass("error"); + + //find all control group's that have no error and ensure the class is removed + var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); + noInError.removeClass("error"); - //get the lengths of each array for each key in the $error collection - var validatorLengths = _.map(formCtrl.$error, function (val, key) { - // if there are child ng-forms, include the $error collections in those as well - var innerErrorCount = _.reduce( - _.map(val, v => - _.reduce( - _.map(v.$error, e => e.length), - (m, n) => m + n - ) - ), - (memo, num) => memo + num - ); - return val.length + innerErrorCount; }); - //sum up all numbers in the resulting array - var sum = _.reduce(validatorLengths, function (memo, num) { - return memo + num; - }, 0); - //this is the value we watch to notify of any validation changes on the form - return sum; - }, function (e) { - scope.$broadcast("valStatusChanged", { form: formCtrl }); - - notifySubView(); - - //find all invalid elements' .control-group's and apply the error class - var inError = element.find(".control-group .ng-invalid").closest(".control-group"); - inError.addClass("error"); - - //find all control group's that have no error and ensure the class is removed - var noInError = element.find(".control-group .ng-valid").closest(".control-group").not(inError); - noInError.removeClass("error"); - - }); //This tracks if the user is currently saving a new item, we use this to determine // if we should display the warning dialog that they are leaving the page - if a new item @@ -119,7 +136,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var isSavingNewItem = false; //we should show validation if there are any msgs in the server validation collection - if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.showValidation)) { + if (serverValidationManager.items.length > 0 || (parentFormMgr && parentFormMgr.isShowingValidation())) { element.addClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = true; notifySubView(); @@ -128,7 +145,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var unsubscribe = []; //listen for the forms saving event - unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function(ev, args) { + unsubscribe.push(scope.$on(SAVING_EVENT_NAME, function (ev, args) { element.addClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = true; notifySubView(); @@ -137,7 +154,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location })); //listen for the forms saved event - unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function(ev, args) { + unsubscribe.push(scope.$on(SAVED_EVENT_NAME, function (ev, args) { //remove validation class element.removeClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = false; @@ -151,7 +168,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location //This handles the 'unsaved changes' dialog which is triggered when a route is attempting to be changed but // the form has pending changes - var locationEvent = $rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) { + var locationEvent = $rootScope.$on('$locationChangeStart', function (event, nextLocation, currentLocation) { var infiniteEditors = editorService.getEditors(); @@ -178,10 +195,10 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location "disableEscKey": true, "submitButtonLabel": labels.stayButton, "closeButtonLabel": labels.discardChangesButton, - submit: function() { + submit: function () { overlayService.close(); }, - close: function() { + close: function () { // close all editors editorService.closeAll(); // allow redirection @@ -215,13 +232,13 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location unsubscribe.push(locationEvent); //Ensure to remove the event handler when this instance is destroyted - scope.$on('$destroy', function() { + scope.$on('$destroy', function () { for (var u in unsubscribe) { unsubscribe[u](); } }); - $timeout(function(){ + $timeout(function () { formCtrl.$setPristine(); }, 1000); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 30d3530efb..979ac23bb3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -8,24 +8,26 @@ * We will listen for server side validation changes * and when an error is detected for this property we'll show the error message. * In order for this directive to work, the valFormManager directive must be placed on the containing form. +* We don't set the validity of this validator to false when client side validation fails, only when server side +* validation fails however we do respond to the client side validation changes to display error and adjust UI state. **/ -function valPropertyMsg(serverValidationManager, localizationService) { +function valPropertyMsg(serverValidationManager, localizationService, angularHelper) { return { - require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent'], + require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent', '?^^valPropertyMsg'], replace: true, restrict: "E", template: "
{{errorMsg}}
", scope: {}, link: function (scope, element, attrs, ctrl) { - + var unsubscribe = []; var watcher = null; - var hasError = false; + var hasError = false; // tracks if there is a child error or an explicit error //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - + scope.errorMsg = ""; + //the property form controller api var formCtrl = ctrl[0]; //the valFormManager controller api @@ -33,21 +35,19 @@ function valPropertyMsg(serverValidationManager, localizationService) { //the property controller api var umbPropCtrl = ctrl[2]; //the variants controller api - var umbVariantCtrl = ctrl[3]; - + var umbVariantCtrl = ctrl[3]; + var currentProperty = umbPropCtrl.property; scope.currentProperty = currentProperty; var currentCulture = currentProperty.culture; - var currentSegment = currentProperty.segment; - + var currentSegment = currentProperty.segment; + // validation object won't exist when editor loads outside the content form (ie in settings section when modifying a content type) var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined; var labels = {}; - localizationService.localize("errors_propertyHasErrors").then(function (data) { - labels.propertyHasErrors = data; - }); + var showValidation = false; if (umbVariantCtrl) { //if we are inside of an umbVariantContent directive @@ -61,17 +61,16 @@ function valPropertyMsg(serverValidationManager, localizationService) { return; } } - + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. currentCulture = currentCulture || "invariant"; - // Gets the error message to display function getErrorMsg() { //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, "", null); + var err = serverValidationManager.getPropertyError(umbPropCtrl.getValidationPath(), null, "", null); //if there's an error message use it if (err && err.errorMsg) { return err.errorMsg; @@ -84,43 +83,108 @@ function valPropertyMsg(serverValidationManager, localizationService) { return labels.propertyHasErrors; } - // We need to subscribe to any changes to our model (based on user input) - // This is required because when we have a server error we actually invalidate - // the form which means it cannot be resubmitted. - // So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. + // check the current errors in the form (and recursive sub forms), if there is 1 or 2 errors + // we can check if those are valPropertyMsg or valServer and can clear our error in those cases. + function checkAndClearError() { + + var errCount = angularHelper.countAllFormErrors(formCtrl); + + if (errCount === 0) { + return true; + } + + if (errCount > 2) { + return false; + } + + var hasValServer = Utilities.isArray(formCtrl.$error.valServer); + if (errCount === 1 && hasValServer) { + return true; + } + + var hasOwnErr = hasExplicitError(); + if ((errCount === 1 && hasOwnErr) || (errCount === 2 && hasOwnErr && hasValServer)) { + + var propertyValidationPath = umbPropCtrl.getValidationPath(); + // check if we can clear it based on child server errors, if we are the only explicit one remaining we can clear ourselves + if (isLastServerError(propertyValidationPath)) { + serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, "", currentSegment); + return true; + } + return false; + } + + return false; + } + + // returns true if there is an explicit valPropertyMsg validation error on the form + function hasExplicitError() { + return Utilities.isArray(formCtrl.$error.valPropertyMsg); + } + + // returns true if there is only a single server validation error for this property validation key in it's validation path + function isLastServerError(propertyValidationPath) { + var nestedErrs = serverValidationManager.getPropertyErrorsByValidationPath( + propertyValidationPath, + currentCulture, + currentSegment, + { matchType: "prefix" }); + if (nestedErrs.length === 0 || (nestedErrs.length === 1 && nestedErrs[0].propertyAlias === propertyValidationPath)) { + + return true; + } + return false; + } + + // a custom $validator function called on when each child ngModelController changes a value. + function resetServerValidityValidator(fieldController) { + const storedFieldController = fieldController; // pin a reference to this + return (modelValue, viewValue) => { + // if the ngModelController value has changed, then we can check and clear the error + if (storedFieldController.$dirty) { + if (checkAndClearError()) { + resetError(); + } + } + return true; // this validator is always 'valid' + }; + } + + // collect all ng-model controllers recursively within the umbProperty form + // until it reaches the next nested umbProperty form + function collectAllNgModelControllersRecursively(controls, ngModels) { + controls.forEach(ctrl => { + if (angularHelper.isForm(ctrl)) { + // if it's not another umbProperty form then recurse + if (ctrl.$name !== formCtrl.$name) { + collectAllNgModelControllersRecursively(ctrl.$getControls(), ngModels); + } + } + else if (ctrl.hasOwnProperty('$modelValue') && Utilities.isObject(ctrl.$validators)) { + ngModels.push(ctrl); + } + }); + } + + // We start the watch when there's server validation errors detected. + // We watch on the current form's properties and on first watch or if they are dynamically changed + // we find all ngModel controls recursively on this form (but stop recursing before we get to the next) + // umbProperty form). Then for each ngModelController we assign a new $validator. This $validator + // will execute whenever the value is changed which allows us to check and reset the server validator function startWatch() { - //if there's not already a watch if (!watcher) { - watcher = scope.$watch("currentProperty.value", - function (newValue, oldValue) { - if (angular.equals(newValue, oldValue)) { - return; - } - var errCount = 0; - - for (var e in formCtrl.$error) { - if (Utilities.isArray(formCtrl.$error[e])) { - errCount++; + watcher = scope.$watchCollection( + () => formCtrl, + function (updatedFormController) { + var ngModels = []; + collectAllNgModelControllersRecursively(updatedFormController.$getControls(), ngModels); + ngModels.forEach(x => { + if (!x.$validators.serverValidityResetter) { + x.$validators.serverValidityResetter = resetServerValidityValidator(x); } - } - - //we are explicitly checking for valServer errors here, since we shouldn't auto clear - // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg - // is the only one, then we'll clear. - - if (errCount === 0 - || (errCount === 1 && Utilities.isArray(formCtrl.$error.valPropertyMsg)) - || (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) { - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - } else if (showValidation && scope.errorMsg === "") { - formCtrl.$setValidity('valPropertyMsg', false); - scope.errorMsg = getErrorMsg(); - } - }, true); + }); + }); } } @@ -132,22 +196,31 @@ function valPropertyMsg(serverValidationManager, localizationService) { } } + function resetError() { + stopWatch(); + hasError = false; + formCtrl.$setValidity('valPropertyMsg', true); + scope.errorMsg = ""; + + } + + // This deals with client side validation changes and is executed anytime validators change on the containing + // valFormManager. This allows us to know when to display or clear the property error data for non-server side errors. function checkValidationStatus() { if (formCtrl.$invalid) { //first we need to check if the valPropertyMsg validity is invalid if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { //since we already have an error we'll just return since this means we've already set the - // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe + //hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe return; } //if there are any errors in the current property form that are not valPropertyMsg else if (_.without(_.keys(formCtrl.$error), "valPropertyMsg").length > 0) { - + // errors exist, but if the property is NOT mandatory and has no value, the errors should be cleared if (isMandatory !== undefined && isMandatory === false && !currentProperty.value) { - hasError = false; - showValidation = false; - scope.errorMsg = ""; + + resetError(); // if there's no value, the controls can be reset, which clears the error state on formCtrl for (let control of formCtrl.$getControls()) { @@ -164,99 +237,107 @@ function valPropertyMsg(serverValidationManager, localizationService) { } } else { - hasError = false; - scope.errorMsg = ""; + resetError(); } } else { - hasError = false; - scope.errorMsg = ""; + resetError(); } } - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - if (!showValidation) { - //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) - //or we can just check upwards in the DOM for the css class (easier for now). - //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot - //reset the status. - showValidation = element.closest(".show-validation").length > 0; - } + function onInit() { + localizationService.localize("errors_propertyHasErrors").then(function (data) { - //listen for form validation changes. - //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then - // subscribing to this single watch. - valFormManager.onValidationStatusChanged(function (evt, args) { - checkValidationStatus(); - }); + labels.propertyHasErrors = data; - //listen for the forms saving event - unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { - showValidation = true; - if (hasError && scope.errorMsg === "") { - scope.errorMsg = getErrorMsg(); - startWatch(); - } - else if (!hasError) { - scope.errorMsg = ""; - stopWatch(); - } - })); - - //listen for the forms saved event - unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { - showValidation = false; - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); - })); - - //listen for server validation changes - // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for - // validation changes to fields in the property this is because some server side validators may not - // return the field name for which the error belongs too, just the property for which it belongs. - // It's important to note that we need to subscribe to server validation changes here because we always must - // indicate that a content property is invalid at the property level since developers may not actually implement - // 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(); + //if there's any remaining errors in the server validation service then we should show them. + showValidation = serverValidationManager.items.length > 0; + if (!showValidation) { + //We can either get the form submitted status by the parent directive valFormManager (if we add a property to it) + //or we can just check upwards in the DOM for the css class (easier for now). + //The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot + //reset the status. + showValidation = element.closest(".show-validation").length > 0; } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - stopWatch(); + + //listen for form validation changes. + //The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then + // subscribing to this single watch. + valFormManager.onValidationStatusChanged(function (evt, args) { + checkValidationStatus(); + }); + + //listen for the forms saving event + unsubscribe.push(scope.$on("formSubmitting", function (ev, args) { + showValidation = true; + if (hasError && scope.errorMsg === "") { + scope.errorMsg = getErrorMsg(); + startWatch(); + } + else if (!hasError) { + resetError(); + } + })); + + //listen for the forms saved event + unsubscribe.push(scope.$on("formSubmitted", function (ev, args) { + showValidation = false; + resetError(); + })); + + if (scope.currentProperty) { //this can be null if no property was assigned + + // listen for server validation changes for property validation path prefix. + // We pass in "" in order to listen for all validation changes to the content property, not for + // validation changes to fields in the property this is because some server side validators may not + // return the field name for which the error belongs too, just the property for which it belongs. + // It's important to note that we need to subscribe to server validation changes here because we always must + // indicate that a content property is invalid at the property level since developers may not actually implement + // the correct field validation in their property editors. + + function serverValidationManagerCallback(isValid, propertyErrors, allErrors) { + var hadError = hasError; + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors.length > 1 ? labels.propertyHasErrors : propertyErrors[0].errorMsg || labels.propertyHasErrors; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + startWatch(); + + + if (propertyErrors.length === 1 && hadError && !formCtrl.$pristine) { + var propertyValidationPath = umbPropCtrl.getValidationPath(); + serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, "", currentSegment); + resetError(); + } + } + else { + resetError(); + } + } + + unsubscribe.push(serverValidationManager.subscribe( + umbPropCtrl.getValidationPath(), + currentCulture, + "", + serverValidationManagerCallback, + currentSegment, + { matchType: "prefix" } // match property validation path prefix + )); } - } - - unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias, - currentCulture, - "", - serverValidationManagerCallback, - currentSegment - ) - ); + }); } //when the scope is disposed we need to unsubscribe scope.$on('$destroy', function () { stopWatch(); - for (var u in unsubscribe) { - unsubscribe[u](); - } + unsubscribe.forEach(u => u()); }); + + onInit(); } 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 3fa9220f7b..ea6087d4e9 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 @@ -13,14 +13,14 @@ function valServer(serverValidationManager) { link: function (scope, element, attr, ctrls) { var modelCtrl = ctrls[0]; - var umbPropCtrl = ctrls.length > 1 ? ctrls[1] : null; + var umbPropCtrl = ctrls[1]; if (!umbPropCtrl) { //we cannot proceed, this validator will be disabled return; } // optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages. - var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null; + var umbVariantCtrl = ctrls[2]; var currentProperty = umbPropCtrl.property; var currentCulture = currentProperty.culture; @@ -55,8 +55,11 @@ function valServer(serverValidationManager) { } } - //Need to watch the value model for it to change, previously we had subscribed to - //modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that + // Get the property validation path if there is one, this is how wiring up any nested/virtual property validation works + var propertyValidationPath = umbPropCtrl ? umbPropCtrl.getValidationPath() : currentProperty.alias; + + // Need to watch the value model for it to change, previously we had subscribed to + // modelCtrl.$viewChangeListeners but this is not good enough if you have an editor that // doesn't specifically have a 2 way ng binding. This is required because when we // have a server error we actually invalidate the form which means it cannot be // resubmitted. So once a field is changed that has a server error assigned to it @@ -75,8 +78,10 @@ function valServer(serverValidationManager) { if (modelCtrl.$invalid) { modelCtrl.$setValidity('valServer', true); + //clear the server validation entry - serverValidationManager.removePropertyError(currentProperty.alias, currentCulture, fieldName, currentSegment); + serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, fieldName, currentSegment); + stopWatch(); } }, true); @@ -105,7 +110,9 @@ function valServer(serverValidationManager) { stopWatch(); } } - unsubscribe.push(serverValidationManager.subscribe(currentProperty.alias, + + unsubscribe.push(serverValidationManager.subscribe( + propertyValidationPath, currentCulture, fieldName, serverValidationManagerCallback, @@ -114,9 +121,7 @@ function valServer(serverValidationManager) { scope.$on('$destroy', function () { stopWatch(); - for (var u in unsubscribe) { - unsubscribe[u](); - } + unsubscribe.forEach(u => u()); }); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valservermatch.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valservermatch.directive.js new file mode 100644 index 0000000000..1b4c593735 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valservermatch.directive.js @@ -0,0 +1,123 @@ +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServerMatch + * @restrict A + * @description A custom validator applied to a form/ng-form within an umbProperty that validates server side validation data + * contained within the serverValidationManager. The data can be matched on "exact", "prefix", "suffix" or "contains" matches against + * a property validation key. The attribute value can be in multiple value types: + * - STRING = The property validation key to have an exact match on. If matched, then the form will have a valServerMatch validator applied. + * - OBJECT = A dictionary where the key is the match type: "contains", "prefix", "suffix" and the value is either: + * - ARRAY = A list of property validation keys to match on. If any are matched then the form will have a valServerMatch validator applied. + * - OBJECT = A dictionary where the key is the validator error name applied to the form and the value is the STRING of the property validation key to match on +**/ +function valServerMatch(serverValidationManager) { + + return { + require: ['form', '^^umbProperty', '?^^umbVariantContent'], + restrict: "A", + scope: { + valServerMatch: "=" + }, + link: function (scope, element, attr, ctrls) { + + var formCtrl = ctrls[0]; + var umbPropCtrl = ctrls[1]; + if (!umbPropCtrl) { + //we cannot proceed, this validator will be disabled + return; + } + + // optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages. + var umbVariantCtrl = ctrls[2]; + + var currentProperty = umbPropCtrl.property; + var currentCulture = currentProperty.culture; + var currentSegment = currentProperty.segment; + + if (umbVariantCtrl) { + //if we are inside of an umbVariantContent directive + + 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 || !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; + } + } + + // if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language. + currentCulture = currentCulture || "invariant"; + + var unsubscribe = []; + + function bindCallback(validationKey, matchVal, matchType) { + + if (Utilities.isString(matchVal)) { + matchVal = [matchVal]; // normalize to an array since the value can also natively be an array + } + + // match for each string in the array + matchVal.forEach(m => { + unsubscribe.push(serverValidationManager.subscribe( + m, + currentCulture, + "", + // the callback + function (isValid, propertyErrors, allErrors) { + if (!isValid) { + formCtrl.$setValidity(validationKey, false); + } + else { + formCtrl.$setValidity(validationKey, true); + } + }, + currentSegment, + matchType ? { matchType: matchType } : null // specify the match type + )); + }); + + } + + if (Utilities.isObject(scope.valServerMatch)) { + var allowedKeys = ["contains", "prefix", "suffix"]; + Object.keys(scope.valServerMatch).forEach(matchType => { + if (allowedKeys.indexOf(matchType) === -1) { + throw "valServerMatch dictionary keys must be one of " + allowedKeys.join(); + } + + var matchVal = scope.valServerMatch[matchType]; + + if (Utilities.isObject(matchVal)) { + + // as an object, the key will be the validation error instead of the default "valServerMatch" + Object.keys(matchVal).forEach(valKey => { + + // matchVal[valKey] can be an ARRAY or a STRING + bindCallback(valKey, matchVal[valKey], matchType); + }); + } + else { + + // matchVal can be an ARRAY or a STRING + bindCallback("valServerMatch", matchVal, matchType); + } + }); + } + else if (Utilities.isString(scope.valServerMatch)) { + + // a STRING match which will be an exact match on the string supplied as the property validation key + bindCallback("valServerMatch", scope.valServerMatch, null); + } + else { + throw "valServerMatch value must be a string or a dictionary"; + } + + scope.$on('$destroy', function () { + unsubscribe.forEach(u => u()); + }); + } + }; +} +angular.module('umbraco.directives.validation').directive("valServerMatch", valServerMatch); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js index 1f5aaaa1c2..97d11879e9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -1,36 +1,49 @@ /** -* @ngdoc directive -* @name umbraco.directives.directive:valSubView -* @restrict A -* @description Used to show validation warnings for a editor sub view to indicate that the section content has validation errors in its data. -* In order for this directive to work, the valFormManager directive must be placed on the containing form. + * @ngdoc directive + * @name umbraco.directives.directive:valSubView + * @restrict A + * @description Used to show validation warnings for a editor sub view (used in conjunction with: + * umb-editor-sub-view or umb-editor-sub-views) to indicate that the section content has validation errors in its data. + * In order for this directive to work, the valFormManager directive must be placed on the containing form. + * When applied to **/ (function () { 'use strict'; + // Since this is a directive applied as an attribute, the value of that attribtue is the 'model' object property + // of the current inherited scope that the hasError/errorClass properties will apply to. + // This directive cannot have it's own scope because it's an attribute applied to another scoped directive. + // Due to backwards compatibility we can't really change this, ideally this would have it's own scope/properties. + function valSubViewDirective() { - function controller($scope, $element) { + function controller($scope, $element, $attrs) { + + var model = $scope.model; // this is the default and required for backwards compat + if ($attrs && $attrs.valSubView) { + // get the property to use + model = $scope[$attrs.valSubView]; + } + //expose api return { valStatusChanged: function (args) { - // TODO: Verify this is correct, does $scope.model ever exist? - if ($scope.model) { + if (model) { if (!args.form.$valid) { var subViewContent = $element.find(".ng-invalid"); if (subViewContent.length > 0) { - $scope.model.hasError = true; - $scope.model.errorClass = args.showValidation ? 'show-validation' : null; + model.hasError = true; + model.errorClass = args.showValidation ? 'show-validation' : null; } else { - $scope.model.hasError = false; - $scope.model.errorClass = null; + model.hasError = false; + model.errorClass = null; } } else { - $scope.model.hasError = false; - $scope.model.errorClass = null; + model.hasError = false; + model.errorClass = null; } } } @@ -40,12 +53,18 @@ function link(scope, el, attr, ctrl) { //if there are no containing form or valFormManager controllers, then we do nothing - if (!ctrl || !Utilities.isArray(ctrl) || ctrl.length !== 2 || !ctrl[0] || !ctrl[1]) { + if (!ctrl[1]) { return; } + var model = scope.model; // this is the default and required for backwards compat + if (attr && attr.valSubView) { + // get the property to use + model = scope[attr.valSubView]; + } + var valFormManager = ctrl[1]; - scope.model.hasError = false; + model.hasError = false; //listen for form validation changes valFormManager.onValidationStatusChanged(function (evt, args) { @@ -54,14 +73,14 @@ var subViewContent = el.find(".ng-invalid"); if (subViewContent.length > 0) { - scope.model.hasError = true; + model.hasError = true; } else { - scope.model.hasError = false; + model.hasError = false; } } else { - scope.model.hasError = false; + model.hasError = false; } }); 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 f07a2a441c..8293443dd4 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 @@ -1,5 +1,5 @@ angular.module('umbraco.mocks'). - factory('mocksUtils', ['$cookies', function ($cookies) { + factory('mocksUtils', ['$cookies', 'udiService', function ($cookies, udiService) { 'use strict'; //by default we will perform authorization @@ -40,13 +40,17 @@ angular.module('umbraco.mocks'). }, /** Creats a mock content object */ - getMockContent: function(id) { + getMockContent: function (id, key, udi) { + key = key || String.CreateGuid(); + var udi = udi || udiService.build("content", key); var node = { name: "My content with id: " + id, updateDate: new Date().toIsoDateTimeString(), publishDate: new Date().toIsoDateTimeString(), createDate: new Date().toIsoDateTimeString(), id: id, + key: key, + udi: udi, parentId: 1234, icon: "icon-umb-content", owner: { name: "Administrator", id: 0 }, @@ -280,6 +284,182 @@ angular.module('umbraco.mocks'). return node; }, + + /** Creats a mock variant content object */ + getMockVariantContent: function(id, key, udi) { + key = key || String.CreateGuid(); + var udi = udi || udiService.build("content", key); + var node = { + name: "My content with id: " + id, + updateDate: new Date().toIsoDateTimeString(), + publishDate: new Date().toIsoDateTimeString(), + createDate: new Date().toIsoDateTimeString(), + id: id, + key: key, + udi: udi, + parentId: 1234, + icon: "icon-umb-content", + owner: { name: "Administrator", id: 0 }, + updater: { name: "Per Ploug Krogslund", id: 1 }, + path: "-1,1234,2455", + allowedActions: ["U", "H", "A"], + contentTypeAlias: "testAlias", + contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", + apps: [], + variants: [ + { + name: "", + language: null, + segment: null, + state: "NotCreated", + updateDate: "0001-01-01 00:00:00", + createDate: "0001-01-01 00:00:00", + publishDate: null, + releaseDate: null, + expireDate: null, + notifications: [], + tabs: [ + { + label: "Content", + id: 2, + properties: [ + { alias: "testproperty", label: "Test property", view: "textbox", value: "asdfghjk" }, + { alias: "valTest", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} } + ] + }, + { + label: "Sample Editor", + id: 3, + properties: [ + { alias: "datepicker", label: "Datepicker", view: "datepicker", config: { pickTime: false, format: "yyyy-MM-dd" } }, + { alias: "tags", label: "Tags", view: "tags", value: "" } + ] + }, + { + label: "This", + id: 4, + properties: [ + { alias: "valTest4", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText4", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea4", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content4", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Is", + id: 5, + properties: [ + { alias: "valTest5", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText5", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea5", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content5", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Overflown", + id: 6, + properties: [ + { alias: "valTest6", label: "Validation test", view: "validationtest", value: "asdfasdf" }, + { alias: "bodyText6", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "

askjdkasj lasjd

", config: {} }, + { alias: "textarea6", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } }, + { alias: "content6", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" } + ] + }, + { + label: "Generic Properties", + id: 0, + properties: [ + { + label: 'Id', + value: 1234, + view: "readonlyvalue", + alias: "_umb_id" + }, + { + label: 'Created by', + description: 'Original author', + value: "Administrator", + view: "readonlyvalue", + alias: "_umb_createdby" + }, + { + label: 'Created', + description: 'Date/time this document was created', + value: new Date().toIsoDateTimeString(), + view: "readonlyvalue", + alias: "_umb_createdate" + }, + { + label: 'Updated', + description: 'Date/time this document was created', + value: new Date().toIsoDateTimeString(), + view: "readonlyvalue", + alias: "_umb_updatedate" + }, + { + label: 'Document Type', + value: "Home page", + view: "readonlyvalue", + alias: "_umb_doctype" + }, + { + label: 'Publish at', + description: 'Date/time to publish this document', + value: new Date().toIsoDateTimeString(), + view: "datepicker", + alias: "_umb_releasedate" + }, + { + label: 'Unpublish at', + description: 'Date/time to un-publish this document', + value: new Date().toIsoDateTimeString(), + view: "datepicker", + alias: "_umb_expiredate" + }, + { + label: 'Template', + value: "myTemplate", + view: "dropdown", + alias: "_umb_template", + config: { + items: { + "" : "-- Choose template --", + "myTemplate" : "My Templates", + "home" : "Home Page", + "news" : "News Page" + } + } + }, + { + label: 'Link to document', + value: ["/testing" + id, "http://localhost/testing" + id, "http://mydomain.com/testing" + id].join(), + view: "urllist", + alias: "_umb_urllist" + }, + { + alias: "test", label: "Stuff", view: "test", value: "", + config: { + fields: [ + { alias: "embedded", label: "Embbeded", view: "textstring", value: "" }, + { alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: "" }, + { alias: "embedded3", label: "Embbeded 3", view: "textarea", value: "" }, + { alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: "" } + ] + } + } + ] + } + ] + } + ] + }; + + return node; + }, + getMockEntity : function(id){ return {name: "hello", id: id, icon: "icon-file"}; }, diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/resources/variantcontent.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/variantcontent.mocks.js new file mode 100644 index 0000000000..3a434bdadc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/resources/variantcontent.mocks.js @@ -0,0 +1,56 @@ +angular.module('umbraco.mocks'). + factory('variantContentMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) { + 'use strict'; + + function returnEmptyVariantNode(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var response = returnVariantNodebyId(200, "", null); + var node = response[1]; + var parentId = mocksUtils.getParameterByName(data, "parentId") || 1234; + + node.name = ""; + node.id = 0; + node.parentId = parentId; + + node.tabs.forEach(function(tab) { + tab.properties.forEach(function(property) { + property.value = ""; + }); + }); + + return response; + } + + function returnVariantNodebyId(status, data, headers) { + + if (!mocksUtils.checkAuth()) { + return [401, null, null]; + } + + var id = mocksUtils.getParameterByName(data, "id") || "1234"; + id = parseInt(id, 10); + + var node = mocksUtils.getMockVariantContent(id); + + return [200, node, null]; + } + + + return { + register: function () { + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetById?')) + .respond(returnVariantNodebyId); + + $httpBackend + .whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Content/GetEmpty')) + .respond(returnEmptyVariantNode); + + } + }; +}]); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index c6efb05e1d..3dfbeade4f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -388,7 +388,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetBlueprintById", - [{ id: id }])), + { id: id })), 'Failed to retrieve data for content id ' + id) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); @@ -401,7 +401,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetNotificationOptions", - [{ contentId: id }])), + { contentId: id })), 'Failed to retrieve data for content id ' + id); }, @@ -498,12 +498,57 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), + { contentTypeAlias: alias, parentId: parentId })), 'Failed to retrieve data for empty content item type ' + alias) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); }); }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffoldByKey + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type Id must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
+         * contentResource.getScaffoldByKey(1234, '...')
+         *    .then(function(scaffold) {
+         *        var myDoc = scaffold;
+          *        myDoc.name = "My new document";
+         *
+         *        contentResource.publish(myDoc, true)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
+          * 
+ * + * @param {Int} parentId id of content item to return + * @param {String} contentTypeGuid contenttype guid to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffoldByKey: function (parentId, contentTypeKey) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmptyByKey", + { contentTypeKey: contentTypeKey, parentId: parentId })), + 'Failed to retrieve data for empty content item id ' + contentTypeKey) + .then(function (result) { + return $q.when(umbDataFormatter.formatContentGetData(result)); + }); + }, getBlueprintScaffold: function (parentId, blueprintId) { @@ -512,7 +557,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId }])), + { blueprintId: blueprintId, parentId: parentId })), 'Failed to retrieve blueprint for id ' + blueprintId) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js new file mode 100644 index 0000000000..acc0a94485 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/elementtype.resource.js @@ -0,0 +1,24 @@ +/** + * @ngdoc service + * @name umbraco.resources.elementTypeResource + * @description Loads in data for element types + **/ +function elementTypeResource($q, $http, umbRequestHelper) { + + return { + + getAll: function () { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "elementTypeApiBaseUrl", + "GetAll")), + "Failed to retrieve element types"); + + } + + }; +} + +angular.module("umbraco.resources").factory("elementTypeResource", elementTypeResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 61d646afc0..838e8f1b80 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -207,7 +207,7 @@ function entityResource($q, $http, umbRequestHelper) { getAnchors: function (rteContent) { if (!rteContent || rteContent.length === 0) { - return []; + return $q.when([]); } return umbRequestHelper.resourcePromise( diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index f2ff711ac9..fd620bac18 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -7,8 +7,86 @@ * Some angular helper/extension methods */ function angularHelper($q) { + + var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"]; + + function collectAllFormErrorsRecursively(formCtrl, allErrors) { + // loop over the error dictionary (see https://docs.angularjs.org/api/ng/type/form.FormController#$error) + var keys = Object.keys(formCtrl.$error); + if (keys.length === 0) { + return; + } + keys.forEach(validationKey => { + var ctrls = formCtrl.$error[validationKey]; + ctrls.forEach(ctrl => { + if (!ctrl) { + // this happens when $setValidity('err', true) is called on a form controller without specifying the 3rd parameter for the control/form + // which just means that this is an error on the formCtrl itself + allErrors.push(formCtrl); // add the error + } + else if (isForm(ctrl)) { + // sometimes the control in error is the same form so we cannot recurse else we'll cause an infinite loop + // and in this case it means the error is assigned directly to the form, not a control + if (ctrl === formCtrl) { + allErrors.push(ctrl); // add the error + return; + } + // recurse with the sub form + collectAllFormErrorsRecursively(ctrl, allErrors); + } + else { + // it's a normal control + allErrors.push(ctrl); // add the error + } + }); + }); + } + + function isForm(obj) { + // a method to check that the collection of object prop names contains the property name expected + function allPropertiesExist(objectPropNames) { + //ensure that every required property name exists on the current object + return _.every(requiredFormProps, function (item) { + return _.contains(objectPropNames, item); + }); + } + + //get the keys of the property names for the current object + var props = _.keys(obj); + //if the length isn't correct, try the next prop + if (props.length < requiredFormProps.length) { + return false; + } + + //ensure that every required property name exists on the current scope property + return allPropertiesExist(props); + } + return { + countAllFormErrors: function (formCtrl) { + var allErrors = []; + collectAllFormErrorsRecursively(formCtrl, allErrors); + return allErrors.length; + }, + + /** + * Will traverse up the $scope chain to all ancestors until the predicate matches for the current scope or until it's at the root. + * @param {any} scope + * @param {any} predicate + */ + traverseScopeChain: function (scope, predicate) { + var s = scope.$parent; + while (s) { + var result = predicate(s); + if (result === true) { + return s; + } + s = s.$parent; + } + return null; + }, + /** * Method used to re-run the $parsers for a given ngModel * @param {} scope @@ -83,6 +161,9 @@ function angularHelper($q) { } }, + + isForm: isForm, + /** * @ngdoc function * @name getCurrentForm @@ -104,31 +185,10 @@ function angularHelper($q) { // is to inject the $element object and use: $element.inheritedData('$formController'); var form = null; - var requiredFormProps = ["$error", "$name", "$dirty", "$pristine", "$valid", "$submitted", "$pending"]; - - // a method to check that the collection of object prop names contains the property name expected - function propertyExists(objectPropNames) { - //ensure that every required property name exists on the current scope property - return _.every(requiredFormProps, function (item) { - - return _.contains(objectPropNames, item); - }); - } for (var p in scope) { - if (_.isObject(scope[p]) && p !== "this" && p.substr(0, 1) !== "$") { - //get the keys of the property names for the current property - var props = _.keys(scope[p]); - //if the length isn't correct, try the next prop - if (props.length < requiredFormProps.length) { - continue; - } - - //ensure that every required property name exists on the current scope property - var containProperty = propertyExists(props); - - if (containProperty) { + if (this.isForm(scope[p])) { form = scope[p]; break; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js new file mode 100644 index 0000000000..ffb1971169 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js @@ -0,0 +1,59 @@ +/** + * @ngdoc service + * @name umbraco.services.blockEditorService + * + * @description + * Added in Umbraco 8.7. Service for dealing with Block Editors. + * + * Block Editor Service provides the basic features for a block editor. + * The main feature is the ability to create a Model Object which takes care of your data for your Block Editor. + * + * + * ##Samples + * + * ####Instantiate a Model Object for your property editor: + * + *
+ *     modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, $scope);
+ *     modelObject.load().then(onLoaded);
+ * 
+ * + * + * See {@link umbraco.services.blockEditorModelObject BlockEditorModelObject} for more samples. + * + */ +(function () { + 'use strict'; + + + function blockEditorService(blockEditorModelObject) { + + /** + * @ngdocs function + * @name createModelObject + * @methodOf umbraco.services.blockEditorService + * + * @description + * Create a new Block Editor Model Object. + * See {@link umbraco.services.blockEditorModelObject blockEditorModelObject} + * + * @see umbraco.services.blockEditorModelObject + * @param {object} propertyModelValue data object of the property editor, usually model.value. + * @param {string} propertyEditorAlias alias of the property. + * @param {object} blockConfigurations block configurations. + * @param {angular-scope} scopeOfExistance A local angularJS scope that exists as long as the data exists. + * @param {angular-scope} propertyEditorScope A local angularJS scope that represents the property editors scope. + * @return {blockEditorModelObject} A instance of the BlockEditorModelObject class. + */ + function createModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope) { + return new blockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope); + } + + return { + createModelObject: createModelObject + } + } + + angular.module('umbraco.services').service('blockEditorService', blockEditorService); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js new file mode 100644 index 0000000000..fe57534ffb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -0,0 +1,815 @@ +/** + * @ngdoc service + * @name umbraco.services.blockEditorModelObject + * + * @description + * Added in Umbraco 8.7. Model Object for dealing with data of Block Editors. + * + * Block Editor Model Object provides the basic features for editing data of a block editor.
+ * Use the Block Editor Service to instantiate the Model Object.
+ * See {@link umbraco.services.blockEditorService blockEditorService} + * + */ +(function () { + 'use strict'; + + + function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService) { + + /** + * Simple mapping from property model content entry to editing model, + * needs to stay simple to avoid deep watching. + */ + function mapToElementModel(elementModel, dataModel) { + + if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; } + + var variant = elementModel.variants[0]; + + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (dataModel[prop.alias]) { + prop.value = dataModel[prop.alias]; + } + } + } + + } + + /** + * Simple mapping from elementModel to property model content entry, + * needs to stay simple to avoid deep watching. + */ + function mapToPropertyModel(elementModel, dataModel) { + + if (!elementModel || !elementModel.variants || !elementModel.variants.length) { return; } + + var variant = elementModel.variants[0]; + + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + if (prop.value) { + dataModel[prop.alias] = prop.value; + } + } + } + + } + + /** + * Map property values from an ElementModel to another ElementModel. + * Used to tricker watchers for synchronization. + * @param {Object} fromModel ElementModel to recive property values from. + * @param {Object} toModel ElementModel to recive property values from. + */ + function mapElementValues(fromModel, toModel) { + if (!fromModel || !fromModel.variants) { + toModel.variants = null; + return; + } + if (!fromModel.variants.length) { + toModel.variants = []; + return; + } + + var fromVariant = fromModel.variants[0]; + if (!fromVariant) { + toModel.variants = [null]; + return; + } + + var toVariant = toModel.variants[0]; + + for (var t = 0; t < fromVariant.tabs.length; t++) { + var fromTab = fromVariant.tabs[t]; + var toTab = toVariant.tabs[t]; + + for (var p = 0; p < fromTab.properties.length; p++) { + var fromProp = fromTab.properties[p]; + var toProp = toTab.properties[p]; + toProp.value = fromProp.value; + } + } + } + + + /** + * Generate label for Block, uses either the labelInterpolator or falls back to the contentTypeName. + * @param {Object} blockObject BlockObject to recive data values from. + */ + function getBlockLabel(blockObject) { + if (blockObject.labelInterpolator !== undefined) { + // We are just using the data model, since its a key/value object that is live synced. (if we need to add additional vars, we could make a shallow copy and apply those.) + return blockObject.labelInterpolator(blockObject.data); + } + return blockObject.content.contentTypeName; + } + + + /** + * Used to add watchers on all properties in a content or settings model + */ + function addWatchers(blockObject, isolatedScope, forSettings) { + var model = forSettings ? blockObject.settings : blockObject.content; + if (!model || !model.variants || !model.variants.length) { return; } + + // Start watching each property value. + var variant = model.variants[0]; + var field = forSettings ? "settings" : "content"; + var watcherCreator = forSettings ? createSettingsModelPropWatcher : createContentModelPropWatcher; + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + + // Watch value of property since this is the only value we want to keep synced. + // Do notice that it is not performing a deep watch, meaning that we are only watching primitive and changes directly to the object of property-value. + // But we like to sync non-primitive values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript. + // Non-primitive values act as references to the same data and are therefor synced. + blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + field + ".variants[0].tabs[" + t + "].properties[" + p + "].value", watcherCreator(blockObject, prop))); + + // We also like to watch our data model to be able to capture changes coming from other places. + if (forSettings === true) { + blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "settingsData" + "." + prop.alias, createLayoutSettingsModelWatcher(blockObject, prop))); + } else { + blockObject.__watchers.push(isolatedScope.$watch("blockObjects._" + blockObject.key + "." + "data" + "." + prop.alias, createDataModelWatcher(blockObject, prop))); + } + } + } + if (blockObject.__watchers.length === 0) { + // If no watcher where created, it means we have no properties to watch. This means that nothing will activate our generate the label, since its only triggered by watchers. + blockObject.updateLabel(); + } + } + + /** + * Used to create a prop watcher for the data in the property editor data model. + */ + function createDataModelWatcher(blockObject, prop) { + return function () { + if (prop.value !== blockObject.data[prop.alias]) { + + // sync data: + prop.value = blockObject.data[prop.alias]; + + blockObject.updateLabel(); + } + } + } + + /** + * Used to create a prop watcher for the settings in the property editor data model. + */ + function createLayoutSettingsModelWatcher(blockObject, prop) { + return function () { + if (prop.value !== blockObject.settingsData[prop.alias]) { + // sync data: + prop.value = blockObject.settingsData[prop.alias]; + } + } + } + + /** + * Used to create a scoped watcher for a content property on a blockObject. + */ + function createContentModelPropWatcher(blockObject, prop) { + return function () { + if (blockObject.data[prop.alias] !== prop.value) { + // sync data: + blockObject.data[prop.alias] = prop.value; + } + + blockObject.updateLabel(); + } + } + + /** + * Used to create a scoped watcher for a settings property on a blockObject. + */ + function createSettingsModelPropWatcher(blockObject, prop) { + return function () { + if (blockObject.settingsData[prop.alias] !== prop.value) { + // sync data: + blockObject.settingsData[prop.alias] = prop.value; + } + } + } + + function createDataEntry(elementTypeKey, dataItems) { + var data = { + contentTypeKey: elementTypeKey, + udi: udiService.create("element") + }; + dataItems.push(data); + return data.udi; + } + + function getDataByUdi(udi, dataItems) { + return dataItems.find(entry => entry.udi === udi) || null; + } + + /** + * Set the udi and key property for the content item + * @param {any} contentData + * @param {any} udi + */ + function ensureUdiAndKey(contentData, udi) { + contentData.udi = udi; + // Change the content.key to the GUID part of the udi, else it's just random which we don't want, it must be consistent + contentData.key = udiService.getKey(udi); + } + + /** + * Used to highlight unsupported properties for the user, changes unsupported properties into a unsupported-property. + */ + var notSupportedProperties = [ + "Umbraco.Tags", + "Umbraco.UploadField", + "Umbraco.ImageCropper" + ]; + + + /** + * Formats the content apps and ensures unsupported property's have the notsupported view (returns a promise) + * @param {any} scaffold + */ + function formatScaffoldData(scaffold) { + + // deal with not supported props + scaffold.variants.forEach((variant) => { + variant.tabs.forEach((tab) => { + tab.properties.forEach((property) => { + if (notSupportedProperties.indexOf(property.editor) !== -1) { + property.view = "notsupported"; + } + }); + }); + }); + + // could be empty in tests + if (!scaffold.apps) { + console.warn("No content apps found in scaffold"); + return $q.resolve(scaffold); + } + + // replace view of content app + + var contentApp = scaffold.apps.find(entry => entry.alias === "umbContent"); + if (contentApp) { + contentApp.view = "views/common/infiniteeditors/blockeditor/blockeditor.content.html"; + } + + // remove info app + var infoAppIndex = scaffold.apps.findIndex(entry => entry.alias === "umbInfo"); + if (infoAppIndex >= 0) { + scaffold.apps.splice(infoAppIndex, 1); + } + + // add the settings app + return localizationService.localize("blockEditor_tabBlockSettings").then( + function (settingsName) { + + var settingsTab = { + "name": settingsName, + "alias": "settings", + "icon": "icon-settings", + "view": "views/common/infiniteeditors/blockeditor/blockeditor.settings.html", + "hasError": false + }; + scaffold.apps.push(settingsTab); + + return scaffold; + } + ); + } + + /** + * @ngdoc method + * @name constructor + * @methodOf umbraco.services.blockEditorModelObject + * @description Constructor of the model object used to handle Block Editor data. + * @param {object} propertyModelValue data object of the property editor, usually model.value. + * @param {string} propertyEditorAlias alias of the property. + * @param {object} blockConfigurations block configurations. + * @param {angular-scope} scopeOfExistance A local angularJS scope that exists as long as the data exists. + * @param {angular-scope} propertyEditorScope A local angularJS scope that represents the property editors scope. + * @returns {BlockEditorModelObject} A instance of BlockEditorModelObject. + */ + function BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, scopeOfExistance, propertyEditorScope) { + + if (!propertyModelValue) { + throw new Error("propertyModelValue cannot be undefined, to ensure we keep the binding to the angular model we need minimum an empty object."); + } + + this.__watchers = []; + + // ensure basic part of data-structure is in place: + this.value = propertyModelValue; + this.value.layout = this.value.layout || {}; + this.value.contentData = this.value.contentData || []; + this.value.settingsData = this.value.settingsData || []; + + this.propertyEditorAlias = propertyEditorAlias; + this.blockConfigurations = blockConfigurations; + + this.scaffolds = []; + + this.isolatedScope = scopeOfExistance.$new(true); + this.isolatedScope.blockObjects = {}; + + this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this))); + this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this))); + + }; + + BlockEditorModelObject.prototype = { + + update: function (propertyModelValue, propertyEditorScope) { + // clear watchers + this.__watchers.forEach(w => { w(); }); + delete this.__watchers; + + // clear block objects + for (const key in this.isolatedScope.blockObjects) { + this.destroyBlockObject(this.isolatedScope.blockObjects[key]); + } + this.isolatedScope.blockObjects = {}; + + // update our values + this.value = propertyModelValue; + this.value.layout = this.value.layout || {}; + this.value.data = this.value.data || []; + + // re-create the watchers + this.__watchers = []; + this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this))); + this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this))); + }, + + /** + * @ngdoc method + * @name getBlockConfiguration + * @methodOf umbraco.services.blockEditorModelObject + * @description Get block configuration object for a given contentTypeKey. + * @param {string} key contentTypeKey to recive the configuration model for. + * @returns {Object | null} Configuration model for the that specific block. Or ´null´ if the contentTypeKey isnt available in the current block configurations. + */ + getBlockConfiguration: function (key) { + return this.blockConfigurations.find(bc => bc.contentTypeKey === key) || null; + }, + + /** + * @ngdoc method + * @name load + * @methodOf umbraco.services.blockEditorModelObject + * @description Load the scaffolding models for the given configuration, these are needed to provide useful models for each block. + * @param {Object} blockObject BlockObject to receive data values from. + * @returns {Promise} A Promise object which resolves when all scaffold models are loaded. + */ + load: function () { + var tasks = []; + + var scaffoldKeys = []; + + this.blockConfigurations.forEach(blockConfiguration => { + scaffoldKeys.push(blockConfiguration.contentTypeKey); + if (blockConfiguration.settingsElementTypeKey != null) { + scaffoldKeys.push(blockConfiguration.settingsElementTypeKey); + } + }); + + // removing duplicates. + scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index); + + var self = this; + + scaffoldKeys.forEach(contentTypeKey => { + tasks.push(contentResource.getScaffoldByKey(-20, contentTypeKey).then(scaffold => { + // self.scaffolds might not exists anymore, this happens if this instance has been destroyed before the load is complete. + if (self.scaffolds) { + return formatScaffoldData(scaffold).then(s => { + self.scaffolds.push(s); + return s; + }); + } + else { + return $q.resolve(scaffold); + } + })); + }); + + return $q.all(tasks); + }, + + /** + * @ngdoc method + * @name getAvailableAliasesForBlockContent + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrive a list of aliases that are available for content of blocks in this property editor, does not contain aliases of block settings. + * @return {Array} array of strings representing alias. + */ + getAvailableAliasesForBlockContent: function () { + return this.blockConfigurations.map(blockConfiguration => this.getScaffoldFromKey(blockConfiguration.contentTypeKey).contentTypeAlias); + }, + + /** + * @ngdoc method + * @name getAvailableBlocksForBlockPicker + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrive a list of available blocks, the list containing object with the confirugation model(blockConfigModel) and the element type model(elementTypeModel). + * The purpose of this data is to provide it for the Block Picker. + * @return {Array} array of objects representing available blocks, each object containing properties blockConfigModel and elementTypeModel. + */ + getAvailableBlocksForBlockPicker: function () { + + var blocks = []; + + this.blockConfigurations.forEach(blockConfiguration => { + var scaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey); + if (scaffold) { + blocks.push({ + blockConfigModel: blockConfiguration, + elementTypeModel: scaffold.documentType + }); + } + }); + + return blocks; + }, + + /** + * @ngdoc method + * @name getScaffoldFromKey + * @methodOf umbraco.services.blockEditorModelObject + * @description Get scaffold model for a given contentTypeKey. + * @param {string} key contentTypeKey to recive the scaffold model for. + * @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context. + */ + getScaffoldFromKey: function (contentTypeKey) { + return this.scaffolds.find(o => o.contentTypeKey === contentTypeKey); + }, + + /** + * @ngdoc method + * @name getScaffoldFromAlias + * @methodOf umbraco.services.blockEditorModelObject + * @description Get scaffold model for a given contentTypeAlias, used by clipboardService. + * @param {string} alias contentTypeAlias to recive the scaffold model for. + * @returns {Object | null} Scaffold model for the that content type. Or null if the scaffolding model dosnt exist in this context. + */ + getScaffoldFromAlias: function (contentTypeAlias) { + return this.scaffolds.find(o => o.contentTypeAlias === contentTypeAlias); + }, + + /** + * @ngdoc method + * @name getBlockObject + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrieve a Block Object for the given layout entry. + * The Block Object offers the necessary data to display and edit a block. + * The Block Object setups live syncronization of content and settings models back to the data of your Property Editor model. + * The returned object, named ´BlockObject´, contains several usefull models to make editing of this block happen. + * The ´BlockObject´ contains the following properties: + * - key {string}: runtime generated key, usefull for tracking of this object + * - content {Object}: Content model, the content data in a ElementType model. + * - settings {Object}: Settings model, the settings data in a ElementType model. + * - config {Object}: A local deep copy of the block configuration model. + * - label {string}: The label for this block. + * - updateLabel {Method}: Method to trigger an update of the label for this block. + * - data {Object}: A reference to the content data object from your property editor model. + * - settingsData {Object}: A reference to the settings data object from your property editor model. + * - layout {Object}: A refernce to the layout entry from your property editor model. + * @param {Object} layoutEntry the layout entry object to build the block model from. + * @return {Object | null} The BlockObject for the given layout entry. Or null if data or configuration wasnt found for this block. + */ + getBlockObject: function (layoutEntry) { + + var contentUdi = layoutEntry.contentUdi; + + var dataModel = getDataByUdi(contentUdi, this.value.contentData); + + if (dataModel === null) { + console.error("Couldn't find content model of " + contentUdi) + return null; + } + + var blockConfiguration = this.getBlockConfiguration(dataModel.contentTypeKey); + var contentScaffold; + + if (blockConfiguration === null) { + console.error("The block entry of " + contentUdi + " is not being initialized because its contentTypeKey is not allowed for this PropertyEditor"); + } + else { + contentScaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey); + if (contentScaffold === null) { + console.error("The block entry of " + contentUdi + " is not begin initialized cause its Element Type was not loaded."); + } + } + + if (blockConfiguration === null || contentScaffold === null) { + + blockConfiguration = { + label: "Unsupported Block", + unsupported: true + }; + contentScaffold = {}; + + } + + var blockObject = {}; + // Set an angularJS cloneNode method, to avoid this object begin cloned. + blockObject.cloneNode = function () { + return null;// angularJS accept this as a cloned value as long as the + } + blockObject.key = String.CreateGuid().replace(/-/g, ""); + blockObject.config = Utilities.copy(blockConfiguration); + if (blockObject.config.label && blockObject.config.label !== "") { + blockObject.labelInterpolator = $interpolate(blockObject.config.label); + } + blockObject.__scope = this.isolatedScope; + blockObject.updateLabel = _.debounce( + function () { + // Check wether scope still exists, maybe object was destoyed in these seconds. + if (this.__scope) { + this.label = getBlockLabel(this); + this.__scope.$evalAsync(); + } + }.bind(blockObject) + , 10); + + // make basics from scaffold + blockObject.content = Utilities.copy(contentScaffold); + ensureUdiAndKey(blockObject.content, contentUdi); + + mapToElementModel(blockObject.content, dataModel); + + blockObject.data = dataModel; + blockObject.layout = layoutEntry; + blockObject.__watchers = []; + + if (blockConfiguration.settingsElementTypeKey) { + var settingsScaffold = this.getScaffoldFromKey(blockConfiguration.settingsElementTypeKey); + if (settingsScaffold !== null) { + + if (!layoutEntry.settingsUdi) { + // if this block does not have settings data, then create it. This could happen because settings model has been added later than this content was created. + layoutEntry.settingsUdi = createDataEntry(blockConfiguration.settingsElementTypeKey, this.value.settingsData); + } + + var settingsUdi = layoutEntry.settingsUdi; + + var settingsData = getDataByUdi(settingsUdi, this.value.settingsData); + if (settingsData === null) { + console.error("Couldnt find content settings data of " + settingsUdi) + return null; + } + + blockObject.settingsData = settingsData; + + // make basics from scaffold + blockObject.settings = Utilities.copy(settingsScaffold); + ensureUdiAndKey(blockObject.settings, settingsUdi); + + mapToElementModel(blockObject.settings, settingsData); + } + } + + blockObject.retrieveValuesFrom = function (content, settings) { + if (this.content !== null) { + mapElementValues(content, this.content); + } + if (this.config.settingsElementTypeKey !== null) { + mapElementValues(settings, this.settings); + } + } + + + blockObject.sync = function () { + if (this.content !== null) { + mapToPropertyModel(this.content, this.data); + } + if (this.config.settingsElementTypeKey !== null) { + mapToPropertyModel(this.settings, this.settingsData); + } + } + + // first time instant update of label. + blockObject.label = getBlockLabel(blockObject); + + // Add blockObject to our isolated scope to enable watching its values: + this.isolatedScope.blockObjects["_" + blockObject.key] = blockObject; + addWatchers(blockObject, this.isolatedScope); + addWatchers(blockObject, this.isolatedScope, true); + + blockObject.destroy = function () { + // remove property value watchers: + this.__watchers.forEach(w => { w(); }); + delete this.__watchers; + + // help carbage collector: + delete this.config; + + delete this.layout; + delete this.data; + delete this.settingsData; + delete this.content; + delete this.settings; + + // remove model from isolatedScope. + delete this.__scope.blockObjects["_" + this.key]; + // NOTE: It seems like we should call this.__scope.$destroy(); since that is the only way to remove a scope from it's parent, + // however that is not the case since __scope is actually this.isolatedScope which gets cleaned up when the outer scope is + // destroyed. If we do that here it breaks the scope chain and validation. + delete this.__scope; + + // removes this method, making it impossible to destroy again. + delete this.destroy; + + // lets remove the key to make things blow up if this is still referenced: + delete this.key; + } + + return blockObject; + + }, + + /** + * @ngdoc method + * @name removeDataAndDestroyModel + * @methodOf umbraco.services.blockEditorModelObject + * @description Removes the data and destroys the Block Model. + * Notice this method does not remove the block from your layout, this will need to be handlede by the Property Editor since this services donst know about your layout structure. + * @param {Object} blockObject The BlockObject to be removed and destroyed. + */ + removeDataAndDestroyModel: function (blockObject) { + var udi = blockObject.content.udi; + var settingsUdi = null; + if (blockObject.settings) { + settingsUdi = blockObject.settings.udi; + } + this.destroyBlockObject(blockObject); + this.removeDataByUdi(udi); + if (settingsUdi) { + this.removeSettingsByUdi(settingsUdi); + } + }, + + /** + * @ngdoc method + * @name destroyBlockObject + * @methodOf umbraco.services.blockEditorModelObject + * @description Destroys the Block Model, but all data is kept. + * @param {Object} blockObject The BlockObject to be destroyed. + */ + destroyBlockObject: function (blockObject) { + blockObject.destroy(); + }, + + /** + * @ngdoc method + * @name getLayout + * @methodOf umbraco.services.blockEditorModelObject + * @description Retrieve the layout object from this specific property editor model. + * @param {object} defaultStructure if no data exist the layout of your poerty editor will be set to this object. + * @return {Object} Layout object, structure depends on the model of your property editor. + */ + getLayout: function (defaultStructure) { + if (!this.value.layout[this.propertyEditorAlias]) { + this.value.layout[this.propertyEditorAlias] = defaultStructure; + } + return this.value.layout[this.propertyEditorAlias]; + }, + + /** + * @ngdoc method + * @name create + * @methodOf umbraco.services.blockEditorModelObject + * @description Create a empty layout entry, notice the layout entry is not added to the property editors model layout object, since the layout sturcture depends on the property editor. + * @param {string} contentTypeKey the contentTypeKey of the block you wish to create, if contentTypeKey is not avaiable in the block configuration then ´null´ will be returned. + * @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or null if contentTypeKey is unavaiaible. + */ + create: function (contentTypeKey) { + + var blockConfiguration = this.getBlockConfiguration(contentTypeKey); + if (blockConfiguration === null) { + return null; + } + + var entry = { + contentUdi: createDataEntry(contentTypeKey, this.value.contentData) + } + + if (blockConfiguration.settingsElementTypeKey != null) { + entry.settingsUdi = createDataEntry(blockConfiguration.settingsElementTypeKey, this.value.settingsData) + } + + return entry; + }, + + /** + * @ngdoc method + * @name createFromElementType + * @methodOf umbraco.services.blockEditorModelObject + * @description Insert data from ElementType Model + * @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or ´null´ if the given ElementType isnt supported by the block configuration. + */ + createFromElementType: function (elementTypeDataModel) { + + elementTypeDataModel = Utilities.copy(elementTypeDataModel); + + var contentTypeKey = elementTypeDataModel.contentTypeKey; + + var layoutEntry = this.create(contentTypeKey); + if (layoutEntry === null) { + return null; + } + + var dataModel = getDataByUdi(layoutEntry.udi, this.value.contentData); + if (dataModel === null) { + return null; + } + + mapToPropertyModel(elementTypeDataModel, dataModel); + + return layoutEntry; + + }, + + /** + * @ngdoc method + * @name sync + * @methodOf umbraco.services.blockEditorModelObject + * @description Force immidiate update of the blockobject models to the property model. + */ + sync: function () { + for (const key in this.isolatedScope.blockObjects) { + this.isolatedScope.blockObjects[key].sync(); + } + }, + + /** + * @ngdoc method + * @name removeDataByUdi + * @methodOf umbraco.services.blockEditorModelObject + * @description Removes the content data of a given UDI. + * Notice this method does not remove the block from your layout, this will need to be handled by the Property Editor since this services don't know about your layout structure. + * @param {string} udi The UDI of the content data to be removed. + */ + removeDataByUdi: function (udi) { + const index = this.value.contentData.findIndex(o => o.udi === udi); + if (index !== -1) { + this.value.contentData.splice(index, 1); + } + }, + + /** + * @ngdoc method + * @name removeSettingsByUdi + * @methodOf umbraco.services.blockEditorModelObject + * @description Removes the settings data of a given UDI. + * Notice this method does not remove the settingsUdi from your layout, this will need to be handled by the Property Editor since this services don't know about your layout structure. + * @param {string} udi The UDI of the settings data to be removed. + */ + removeSettingsByUdi: function (udi) { + const index = this.value.settingsData.findIndex(o => o.udi === udi); + if (index !== -1) { + this.value.settingsData.splice(index, 1); + } + }, + + /** + * @ngdoc method + * @name destroy + * @methodOf umbraco.services.blockEditorModelObject + * @description Notice you should not need to destroy the BlockEditorModelObject since it will automaticly be destroyed when the scope of existance gets destroyed. + */ + destroy: function () { + + this.__watchers.forEach(w => { w(); }); + for (const key in this.isolatedScope.blockObjects) { + this.destroyBlockObject(this.isolatedScope.blockObjects[key]); + } + + delete this.__watchers; + delete this.value; + delete this.propertyEditorAlias; + delete this.blockConfigurations; + delete this.scaffolds; + this.isolatedScope.$destroy(); + delete this.isolatedScope; + delete this.destroy; + } + } + + return BlockEditorModelObject; + } + + angular.module('umbraco.services').service('blockEditorModelObject', blockEditorModelObjectFactory); + +})(); 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 083b4e86b7..cb583546a5 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 @@ -97,6 +97,38 @@ function clipboardService(notificationsService, eventsService, localStorageServi var service = {}; + + /** + * @ngdoc method + * @name umbraco.services.clipboardService#registrerClearPropertyResolver + * @methodOf umbraco.services.clipboardService + * + * @param {string} function A method executed for every property and inner properties copied. + * + * @description + * Executed for all properties including inner properties when performing a copy action. + * + * @deprecated Incorrect spelling please use 'registerClearPropertyResolver' + */ + service.registrerClearPropertyResolver = function(resolver) { + this.registerClearPropertyResolver(resolver); + }; + + /** + * @ngdoc method + * @name umbraco.services.clipboardService#registerClearPropertyResolver + * @methodOf umbraco.services.clipboardService + * + * @param {string} function A method executed for every property and inner properties copied. + * + * @description + * Executed for all properties including inner properties when performing a copy action. + */ + service.registerClearPropertyResolver = function(resolver) { + clearPropertyResolvers.push(resolver); + }; + + /** * @ngdoc method * @name umbraco.services.clipboardService#registrerPropertyClearingResolver 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 a74e3eedb1..562a9766a6 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 @@ -32,6 +32,26 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt return true; } + function showNotificationsForModelsState(ms) { + for (const [key, value] of Object.entries(ms)) { + + var errorMsg = value[0]; + // if the error message is json it's a complex editor validation response that we need to parse + if ((Utilities.isString(errorMsg) && errorMsg.startsWith("[")) || Utilities.isArray(errorMsg)) { + // flatten the json structure, create validation paths for each property and add each as a property error + var idsToErrors = serverValidationManager.parseComplexEditorError(errorMsg, ""); + idsToErrors.forEach(x => { + if (x.modelState) { + showNotificationsForModelsState(x.modelState); + } + }); + } + else if (value[0]) { + notificationsService.error("Validation", value[0]); + } + } + } + return { //TODO: We need to move some of this to formHelper for saving, too many editors use this method for saving when this entire @@ -612,9 +632,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //add model state errors to notifications if (args.showNotifications) { - for (var e in args.err.data.ModelState) { - notificationsService.error("Validation", args.err.data.ModelState[e][0]); - } + showNotificationsForModelsState(args.err.data.ModelState); } if (!this.redirectToCreatedContent(args.err.data.id, args.softRedirect) || args.softRedirect) { 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 5f59a72159..d9c11770cc 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 @@ -46,6 +46,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); this.focusOnFirstError(currentForm); + args.scope.$broadcast("postFormSubmitting", { scope: args.scope, action: args.action }); //then check if the form is valid if (!args.skipValidation) { @@ -55,8 +56,13 @@ function formHelper(angularHelper, serverValidationManager, notificationsService } } - //reset the server validations - serverValidationManager.reset(); + //reset the server validations if required (default is true), otherwise notify existing ones of changes + if (!args.keepServerValidation) { + serverValidationManager.reset(); + } + else { + serverValidationManager.notify(); + } return true; }, @@ -174,73 +180,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService * @param {object} err The error object returned from the http promise */ handleServerValidation: function (modelState) { - for (var e in modelState) { - - //This is where things get interesting.... - // We need to support validation for all editor types such as both the content and content type editors. - // The Content editor ModelState is quite specific with the way that Properties are validated especially considering - // that each property is a User Developer property editor. - // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations - // system. - // So, to do this there's some special ModelState syntax we need to know about. - // For Content Properties, which are user defined, we know that they will exist with a prefixed - // ModelState of "_Properties.", so if we detect this, then we know it's for a content Property. - - //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 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 - // user defined properties with custom controls. - if (parts.length > 1 && parts[0] === "_Properties") { - - var propertyAlias = parts[1]; - - var culture = null; - if (parts.length > 2) { - culture = parts[2]; - //special check in case the string is formatted this way - if (culture === "null") { - culture = null; - } - } - - var segment = null; - if (parts.length > 3) { - segment = parts[3]; - //special check in case the string is formatted this way - if (segment === "null") { - segment = null; - } - } - - 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: - // Groups[0].Properties[2].Alias - serverValidationManager.addFieldError(e, modelState[e][0]); - } - - } + serverValidationManager.addErrorsForModelState(modelState); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index 6081cbd9ad..f8493ab39d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -67,6 +67,9 @@ angular.module('umbraco.services') // loads the language resource file from the server initLocalizedResources: function () { + + // TODO: This promise handling is super ugly, we should just be returnning the promise from $http and returning inner values. + var deferred = $q.defer(); if (resourceFileLoadStatus === "loaded") { @@ -179,7 +182,7 @@ angular.module('umbraco.services') */ localize: function (value, tokens, fallbackValue) { return service.initLocalizedResources().then(function (dic) { - return _lookup(value, tokens, dic, fallbackValue); + return _lookup(value, tokens, dic, fallbackValue); }); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index 13cdd8bdea..ea05dad4e7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -78,7 +78,12 @@ case "delete": if (!overlay.confirmMessageStyle) overlay.confirmMessageStyle = "danger"; if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "danger"; - if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "contentTypeEditor_yesDelete"; + if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "actions_delete"; + break; + + case "remove": + if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "primary"; + if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "actions_remove"; break; default: @@ -94,12 +99,18 @@ confirm(overlay); } + function confirmRemove(overlay) { + overlay.confirmType = "remove"; + confirm(overlay); + } + var service = { open: open, close: close, ysod: ysod, confirm: confirm, - confirmDelete: confirmDelete + confirmDelete: confirmDelete, + confirmRemove: confirmRemove }; return service; 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 718e44d66e..731cc5ed36 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 @@ -7,37 +7,80 @@ * Used to handle server side validation and wires up the UI with the messages. There are 2 types of validation messages, one * is for user defined properties (called Properties) and the other is for field properties which are attached to the native * model objects (not user defined). The methods below are named according to these rules: Properties vs Fields. + * + * For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + * https://github.com/umbraco/Umbraco-CMS/pull/8339 + * */ function serverValidationManager($timeout) { + // The array of callback objects, each object is: + // - propertyAlias (this is the property's 'path' if it's a nested error) + // - culture + // - fieldName + // - segment + // - callback (function) + // - id (unique identifier, auto-generated, used internally for unsubscribing the callback) + // - options (used for complex properties, can contain options.matchType which can be either "suffix" or "prefix" or "contains") var callbacks = []; - - /** calls the callback specified with the errors specified, used internally */ - function executeCallback(self, errorsForCallback, callback, culture, segment) { - callback.apply(self, [ - false, // pass in a value indicating it is invalid + // The array of error message objects, each object 'key' is: + // - propertyAlias (this is the property's 'path' if it's a nested error) + // - culture + // - fieldName + // - segment + // The object also contains: + // - errorMsg + var errorMsgItems = []; + + var defaultMatchOptions = { + matchType: null + } + + /** calls the callback specified with the errors specified, used internally */ + function executeCallback(errorsForCallback, callback, culture, segment, isValid) { + + callback.apply(instance, [ + isValid, // pass in a value indicating it is invalid errorsForCallback, // pass in the errors for this item - self.items, // pass in all errors in total + errorMsgItems, // pass in all errors in total culture, // pass the culture that we are listing for. segment // pass the segment that we are listing for. ] ); } - function getFieldErrors(self, fieldName) { + /** + * @ngdoc function + * @name notify + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Notifies all subscriptions again. Called when there are changes to subscriptions or errors. + */ + function notify() { + + $timeout(function () { + for (var i = 0; i < errorMsgItems.length; i++) { + var item = errorMsgItems[i]; + } + notifyCallbacks(); + }); + } + + function getFieldErrors(fieldName) { if (!Utilities.isString(fieldName)) { throw "fieldName must be a string"; } //find errors for this field name - return _.filter(self.items, function (item) { + return _.filter(errorMsgItems, function (item) { return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName); }); } - - function getPropertyErrors(self, propertyAlias, culture, segment, fieldName) { + function getPropertyErrors(propertyAlias, culture, segment, fieldName, options) { if (!Utilities.isString(propertyAlias)) { throw "propertyAlias must be a string"; } @@ -52,13 +95,50 @@ function serverValidationManager($timeout) { segment = null; } + if (!options) { + options = defaultMatchOptions; + } + //find all errors for this property - return _.filter(self.items, function (item) { - return (item.propertyAlias === propertyAlias && item.culture === culture && item.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + return _.filter(errorMsgItems, function (errMsgItem) { + + if (!errMsgItem.propertyAlias) { + return false; + } + + var matchProp = matchErrMsgItemProperty(errMsgItem, propertyAlias, options); + + return matchProp + && errMsgItem.culture === culture + && errMsgItem.segment === segment + // ignore field matching if match options are used + && (options.matchType || (errMsgItem.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); }); } - - function getVariantErrors(self, culture, segment) { + + /** + * Returns true if the error message item's data matches the property validation key with a match type provided by the options + * @param {any} errMsgItem The error message item + * @param {any} propertyValidationKey The property validation key) + * @param {any} options The match type options + */ + function matchErrMsgItemProperty(errMsgItem, propertyValidationKey, options) { + if (errMsgItem.propertyAlias === propertyValidationKey) { + return true; + } + if (options.matchType === "prefix" && errMsgItem.propertyAlias.startsWith(propertyValidationKey + '/')) { + return true; + } + if (options.matchType === "suffix" && errMsgItem.propertyAlias.endsWith('/' + propertyValidationKey)) { + return true; + } + if (options.matchType === "contains" && errMsgItem.propertyAlias.includes('/' + propertyValidationKey + '/')) { + return true; + } + return false; + } + + function getVariantErrors(culture, segment) { if (!culture) { culture = "invariant"; @@ -68,39 +148,453 @@ function serverValidationManager($timeout) { } //find all errors for this property - return _.filter(self.items, function (item) { + return _.filter(errorMsgItems, function (item) { return (item.culture === culture && item.segment === segment); }); } - function notifyCallbacks(self) { - for (var cb in callbacks) { - if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) { - //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, callbacks[cb].segment); - } + function notifyCallback(cb) { + if (cb.propertyAlias === null && cb.fieldName !== null) { + //its a field error callback + const fieldErrors = getFieldErrors(cb.fieldName); + const valid = fieldErrors.length === 0; + executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment, valid); + } + else if (cb.propertyAlias != null) { + //its a property error + const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.options); + const valid = propErrors.length === 0; + executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid); + } + else { + //its a variant error + const variantErrors = getVariantErrors(cb.culture, cb.segment); + const valid = variantErrors.length === 0; + executeCallback(variantErrors, cb.callback, cb.culture, cb.segment, valid); + } + } + + /** Call all registered callbacks indicating if the data they are subscribed to is valid or invalid */ + function notifyCallbacks() { + + // nothing to call + if (errorMsgItems.length === 0) { + return; + } + + callbacks.forEach(cb => notifyCallback(cb)); + } + + /** + * Flattens the complex errror result json into an array of the block's id/parent id and it's corresponding validation ModelState + * @param {any} errorMsg + * @param {any} parentPropertyAlias The parent property alias for the json error + */ + function parseComplexEditorError(errorMsg, parentPropertyAlias) { + + var json = Utilities.isArray(errorMsg) ? errorMsg : JSON.parse(errorMsg); + + var result = []; + + function extractModelState(validation, parentPath) { + if (validation.$id && validation.ModelState) { + var ms = { + validationPath: `${parentPath}/${validation.$id}`, + modelState: validation.ModelState + }; + result.push(ms); + return ms; } - else if (callbacks[cb].propertyAlias != null) { - //its a property error - 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, callbacks[cb].segment); + return null; + } + + function iterateErrorBlocks(blocks, parentPath) { + for (var i = 0; i < blocks.length; i++) { + var validation = blocks[i]; + var ms = extractModelState(validation, parentPath); + if (!ms) { + continue; } - } - else { - //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); + var nested = _.omit(validation, "$id", "$elementTypeAlias", "ModelState"); + for (const [key, value] of Object.entries(nested)) { + if (Array.isArray(value)) { + iterateErrorBlocks(value, `${ms.validationPath}/${key}`); // recurse + } } } } + + iterateErrorBlocks(json, parentPropertyAlias); + + return result; } - - return { - + + /** + * @ngdoc function + * @name getPropertyCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. + * 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. + */ + function getPropertyCallbacks(propertyAlias, culture, fieldName, segment) { + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } + + var found = _.filter(callbacks, function (cb) { + + if (!cb.options) { + cb.options = defaultMatchOptions; + } + + var matchProp = matchCallbackItemProperty(cb, propertyAlias); + + //returns any callback that have been registered directly against the field and for only the property + return matchProp + && cb.culture === culture + && cb.segment === segment + // ignore field matching if match options are used + && (cb.options.matchType || (cb.fieldName === fieldName || (cb.fieldName === undefined || cb.fieldName === ""))); + }); + return found; + } + + /** + * Returns true if the callback item's data and match options matches the property validation key + * @param {any} cb + * @param {any} propertyValidationKey + */ + function matchCallbackItemProperty(cb, propertyValidationKey) { + if (cb.propertyAlias === propertyValidationKey) { + return true; + } + if (cb.options.matchType === "prefix" && propertyValidationKey.startsWith(cb.propertyAlias + '/')) { + return true; + } + if (cb.options.matchType === "suffix" && propertyValidationKey.endsWith('/' + cb.propertyAlias)) { + return true; + } + if (cb.options.matchType === "contains" && propertyValidationKey.includes('/' + cb.propertyAlias + '/')) { + return true; + } + return false; + } + + /** + * @ngdoc function + * @name getFieldCallbacks + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Gets all callbacks that has been registered using the subscribe method for the field. + */ + function getFieldCallbacks(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.segment === null && item.fieldName === fieldName); + }); + 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. + */ + function getVariantCallbacks(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; + } + + /** + * @ngdoc function + * @name addFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') + */ + function addFieldError(fieldName, errorMsg) { + if (!fieldName) { + return; + } + + //only add the item if it doesn't exist + if (!hasFieldError(fieldName)) { + errorMsgItems.push({ + propertyAlias: null, + culture: "invariant", + segment: null, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + + //find all errors for this item + var errorsForCallback = getFieldErrors(fieldName); + //we should now call all of the call backs registered for this error + var cbs = getFieldCallbacks(fieldName); + //call each callback for this error + for (var cb in cbs) { + executeCallback(errorsForCallback, cbs[cb].callback, null, null, false); + } + } + + /** + * @ngdoc function + * @name addPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Adds an error message for the content property + */ + function addPropertyError(propertyAlias, culture, fieldName, errorMsg, segment) { + + if (!propertyAlias) { + return; + } + + //normalize culture to "invariant" + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } + //normalize errorMsg to empty + if (!errorMsg) { + errorMsg = ""; + } + + // remove all non printable chars and whitespace from the string + // (this can be a json string for complex errors and in some test cases contains odd whitespace) + if (Utilities.isString(errorMsg)) { + errorMsg = errorMsg.trimStartSpecial().trim(); + } + + // if the error message is json it's a complex editor validation response that we need to parse + if ((Utilities.isString(errorMsg) && errorMsg.startsWith("[")) || Utilities.isArray(errorMsg)) { + + // flatten the json structure, create validation paths for each property and add each as a property error + var idsToErrors = parseComplexEditorError(errorMsg, propertyAlias); + idsToErrors.forEach(x => addErrorsForModelState(x.modelState, x.validationPath)); + + // We need to clear the error message else it will show up as a giant json block against the property + errorMsg = ""; + } + + //only add the item if it doesn't exist + if (!hasPropertyError(propertyAlias, culture, fieldName, segment)) { + errorMsgItems.push({ + propertyAlias: propertyAlias, + culture: culture, + segment: segment, + fieldName: fieldName, + errorMsg: errorMsg + }); + } + } + + /** + * @ngdoc function + * @name hasPropertyError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if the content property + culture + field name combo has an error + */ + function hasPropertyError(propertyAlias, culture, fieldName, segment) { + + //normalize culture to null + if (!culture) { + culture = "invariant"; + } + //normalize segment to null + if (!segment) { + segment = null; + } + + var err = _.find(errorMsgItems, 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.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); + }); + return err ? true : false; + } + + /** + * @ngdoc function + * @name hasFieldError + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Checks if a content field has an error + */ + function hasFieldError(fieldName) { + var err = _.find(errorMsgItems, 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.segment === null && item.fieldName === fieldName); + }); + return err ? true : false; + } + + /** + * @ngdoc function + * @name addErrorsForModelState + * @methodOf umbraco.services.serverValidationManager + * @param {any} modelState + * @param {any} parentValidationPath optional parameter specifying a nested element's UDI for which this property belongs (for complex editors) + * @description + * This wires up all of the server validation model state so that valServer and valServerField directives work + */ + function addErrorsForModelState(modelState, parentValidationPath) { + + if (!Utilities.isObject(modelState)) { + throw "modelState is not an object"; + } + + for (const [key, value] of Object.entries(modelState)) { + + //This is where things get interesting.... + // We need to support validation for all editor types such as both the content and content type editors. + // The Content editor ModelState is quite specific with the way that Properties are validated especially considering + // that each property is a User Developer property editor. + // The way that Content Type Editor ModelState is created is simply based on the ASP.Net validation data-annotations + // system. + // So, to do this there's some special ModelState syntax we need to know about. + // For Content Properties, which are user defined, we know that they will exist with a prefixed + // ModelState of "_Properties.", so if we detect this, then we know it's for a content Property. + + //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 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 = key.split("."); + + //Check if this is for content properties - specific to content/media/member editors because those are special + // user defined properties with custom controls. + if (parts.length > 1 && parts[0] === "_Properties") { + + // create the validation key, might just be the prop alias but if it's nested will be validation path + // like "myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/7170A4DD-2441-4B1B-A8D3-437D75C4CBC9/city" + var propertyValidationKey = createPropertyValidationKey(parts[1], parentValidationPath); + + var culture = null; + if (parts.length > 2) { + culture = parts[2]; + //special check in case the string is formatted this way + if (culture === "null") { + culture = null; + } + } + + var segment = null; + if (parts.length > 3) { + segment = parts[3]; + //special check in case the string is formatted this way + if (segment === "null") { + segment = null; + } + } + + var htmlFieldReference = ""; + if (parts.length > 4) { + htmlFieldReference = parts[4] || ""; + } + + // add a generic error for the property + addPropertyError(propertyValidationKey, culture, htmlFieldReference, value && Array.isArray(value) && value.length > 0 ? value[0] : null, segment); + } + else { + + //Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example: + // Groups[0].Properties[2].Alias + addFieldError(key, value[0]); + } + } + + if (hasPropertyError) { + // ensure all callbacks are called after property errors are added + notifyCallbacks(); + } + } + + function createPropertyValidationKey(propertyAlias, parentValidationPath) { + return parentValidationPath ? (parentValidationPath + "/" + propertyAlias) : propertyAlias; + } + + /** + * @ngdoc function + * @name reset + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form + */ + function reset() { + clear(); + for (var cb in callbacks) { + callbacks[cb].callback.apply(instance, [ + true, //pass in a value indicating it is VALID + [], //pass in empty collection + [], + null, + null] + ); + } + } + + /** + * @ngdoc function + * @name clear + * @methodOf umbraco.services.serverValidationManager + * @function + * + * @description + * Clears all errors + */ + function clear() { + errorMsgItems = []; + } + + var instance = { + + addErrorsForModelState: addErrorsForModelState, + parseComplexEditorError: parseComplexEditorError, + createPropertyValidationKey: createPropertyValidationKey, + /** * @ngdoc function * @name notifyAndClearAllSubscriptions @@ -108,42 +602,27 @@ function serverValidationManager($timeout) { * @function * * @description - * This method needs to be called once all field and property errors are wired up. + * This method can be called once all field and property errors are wired up. * * In some scenarios where the error collection needs to be persisted over a route change * (i.e. when a content item (or any item) is created and the route redirects to the editor) * the controller should call this method once the data is bound to the scope * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. + * + * In the case of content with complex editors, variants and different views, those editors don't call this method and instead + * manage the server validation manually by calling notify when necessary and clear/reset when necessary. */ notifyAndClearAllSubscriptions: function() { - var self = this; - $timeout(function () { - notifyCallbacks(self); + notifyCallbacks(); //now that they are all executed, we're gonna clear all of the errors we have - self.clear(); + clear(); }); }, - /** - * @ngdoc function - * @name notify - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * This method isn't used very often but can be used if all subscriptions need to be notified again. This can be - * handy if a view needs to be reloaded/rebuild like when switching variants in the content editor. - */ - notify: function() { - var self = this; - - $timeout(function () { - notifyCallbacks(self); - }); - }, + notify: notify, /** * @ngdoc function @@ -158,7 +637,7 @@ 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, segment) { + subscribe: function (propertyAlias, culture, fieldName, callback, segment, options) { if (!callback) { return; } @@ -173,30 +652,34 @@ function serverValidationManager($timeout) { if (!segment) { segment = null; } - + + let cb = null; + if (propertyAlias === null) { - callbacks.push({ + cb = { propertyAlias: null, culture: culture, segment: segment, fieldName: fieldName, callback: callback, id: id - }); + }; } else if (propertyAlias !== undefined) { - //normalize culture to null - - callbacks.push({ + + cb = { propertyAlias: propertyAlias, - culture: culture, + culture: culture, segment: segment, fieldName: fieldName, callback: callback, - id: id - }); + id: id, + options: options + }; } + callbacks.push(cb); + function unsubscribeId() { //remove all callbacks for the content field callbacks = _.reject(callbacks, function (item) { @@ -204,6 +687,9 @@ function serverValidationManager($timeout) { }); } + // Notify the new callback + notifyCallback(cb); + //return a function to unsubscribe this subscription by uniqueId return unsubscribeId; }, @@ -244,52 +730,8 @@ function serverValidationManager($timeout) { } }, - - /** - * @ngdoc function - * @name getPropertyCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the propertyAlias + fieldName combo. - * 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, segment) { - - //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.segment === segment && (item.fieldName === fieldName || (item.fieldName === undefined || item.fieldName === ""))); - }); - return found; - }, - - /** - * @ngdoc function - * @name getFieldCallbacks - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Gets all callbacks that has been registered using the subscribe method for the field. - */ - 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.segment === null && item.fieldName === fieldName); - }); - return found; - }, + getPropertyCallbacks: getPropertyCallbacks, + getFieldCallbacks: getFieldCallbacks, /** * @ngdoc function @@ -308,107 +750,12 @@ function serverValidationManager($timeout) { 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; - }, - - /** - * @ngdoc function - * @name addFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for a native content item field (not a user defined property, for Example, 'Name') - */ - addFieldError: function(fieldName, errorMsg) { - if (!fieldName) { - return; - } - - //only add the item if it doesn't exist - if (!this.hasFieldError(fieldName)) { - this.items.push({ - propertyAlias: null, - culture: "invariant", - segment: null, - fieldName: fieldName, - errorMsg: errorMsg - }); - } - - //find all errors for this item - var errorsForCallback = getFieldErrors(this, fieldName); - //we should now call all of the call backs registered for this error - var cbs = this.getFieldCallbacks(fieldName); - //call each callback for this error - for (var cb in cbs) { - executeCallback(this, errorsForCallback, cbs[cb].callback, null, null); - } - }, + getVariantCallbacks: getVariantCallbacks, + addFieldError: addFieldError, - /** - * @ngdoc function - * @name addPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Adds an error message for the content property - */ addPropertyError: function (propertyAlias, culture, fieldName, errorMsg, segment) { - if (!propertyAlias) { - return; - } - - //normalize culture to "invariant" - 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, 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, segment, fieldName); - //we should now call all of the call backs registered for this error - 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, segment); - } - - //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 variantCbs) { - executeCallback(this, errorsForCallback, variantCbs[cb].callback, culture, segment); - } + addPropertyError(propertyAlias, culture, fieldName, errorMsg, segment); + notifyCallbacks(); // ensure all callbacks are called }, /** @@ -420,61 +767,19 @@ function serverValidationManager($timeout) { * @description * Removes an error message for the content property */ - removePropertyError: function (propertyAlias, culture, fieldName, segment) { + removePropertyError: function (propertyAlias, culture, fieldName, segment, options) { - if (!propertyAlias) { - return; - } + var errors = getPropertyErrors(propertyAlias, culture, segment, fieldName, options); + errorMsgItems = errorMsgItems.filter(v => errors.indexOf(v) === -1); - //normalize culture to null - 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.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - }, - - /** - * @ngdoc function - * @name reset - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors and notifies all callbacks that all server errros are now valid - used when submitting a form - */ - reset: function () { - this.clear(); - for (var cb in callbacks) { - callbacks[cb].callback.apply(this, [ - true, //pass in a value indicating it is VALID - [], //pass in empty collection - [], - null, - null] - ); + if (errors.length > 0) { + // removal was successful, re-notify all subscribers + notifyCallbacks(); } }, - /** - * @ngdoc function - * @name clear - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Clears all errors - */ - clear: function() { - this.items = []; - }, + reset: reset, + clear: clear, /** * @ngdoc function @@ -486,23 +791,17 @@ function serverValidationManager($timeout) { * Gets the error message for the content property */ getPropertyError: function (propertyAlias, culture, fieldName, segment) { - - //normalize culture to "invariant" - if (!culture) { - culture = "invariant"; + var errors = getPropertyErrors(propertyAlias, culture, segment, fieldName); + if (errors.length > 0) { // should only ever contain one + return errors[0]; } - //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.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err; + return undefined; }, - + + getPropertyErrorsByValidationPath: function (propertyValidationPath, culture, segment, options) { + return getPropertyErrors(propertyValidationPath, culture, segment, "", options); + }, + /** * @ngdoc function * @name getFieldError @@ -513,56 +812,15 @@ function serverValidationManager($timeout) { * Gets the error message for a content field */ getFieldError: function (fieldName) { - var err = _.find(this.items, function (item) { + var err = _.find(errorMsgItems, 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.segment === null && item.fieldName === fieldName); }); return err; }, - /** - * @ngdoc function - * @name hasPropertyError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if the content property + culture + field name combo has an error - */ - 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.segment === segment && (item.fieldName === fieldName || (fieldName === undefined || fieldName === ""))); - }); - return err ? true : false; - }, - - /** - * @ngdoc function - * @name hasFieldError - * @methodOf umbraco.services.serverValidationManager - * @function - * - * @description - * Checks if a content field has an error - */ - 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.segment === null && item.fieldName === fieldName); - }); - return err ? true : false; - }, + hasPropertyError: hasPropertyError, + hasFieldError: hasFieldError, /** * @ngdoc function @@ -580,7 +838,7 @@ function serverValidationManager($timeout) { culture = "invariant"; } - var err = _.find(this.items, function (item) { + var err = _.find(errorMsgItems, function (item) { return (item.culture === culture && item.segment === null); }); return err ? true : false; @@ -606,14 +864,25 @@ function serverValidationManager($timeout) { segment = null; } - var err = _.find(this.items, function (item) { + var err = _.find(errorMsgItems, function (item) { return (item.culture === culture && item.segment === segment); }); return err ? true : false; - }, - /** The array of error messages */ - items: [] + } + }; + + // Used to return the 'items' array as a reference/getter + Object.defineProperty(instance, "items", { + get: function () { + return errorMsgItems; + }, + set: function (value) { + throw "Cannot set the items array"; + } + }); + + return instance; } angular.module('umbraco.services').factory('serverValidationManager', serverValidationManager); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js b/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js new file mode 100644 index 0000000000..7b08c68e4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/udi.service.js @@ -0,0 +1,51 @@ +(function () { + "use strict"; + + /** + * @ngdoc service + * @name umbraco.services.udiService + * @description A service for UDIs + **/ + function udiService() { + return { + + /** + * @ngdoc method + * @name umbraco.services.udiService#create + * @methodOf umbraco.services.udiService + * @function + * + * @description + * Generates a Udi string with a new ID + * + * @param {string} entityType The entityType as a string. + * @returns {string} The generated UDI + */ + create: function(entityType) { + return this.build(entityType, String.CreateGuid()); + }, + + build: function (entityType, guid) { + return "umb://" + entityType + "/" + (guid.replace(/-/g, "")); + }, + + getKey: function (udi) { + if (!Utilities.isString(udi)) { + throw "udi is not a string"; + } + if (!udi.startsWith("umb://")) { + throw "udi does not start with umb://"; + } + var withoutScheme = udi.substr("umb://".length); + var withoutHost = withoutScheme.substr(withoutScheme.indexOf("/") + 1).trim(); + if (withoutHost.length !== 32) { + throw "udi is not 32 chars"; + } + return `${withoutHost.substr(0, 8)}-${withoutHost.substr(8, 4)}-${withoutHost.substr(12, 4)}-${withoutHost.substr(16, 4)}-${withoutHost.substr(20)}`; + } + } + } + + angular.module("umbraco.services").factory("udiService", udiService); + +})(); 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 80898f3004..cc529abb56 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 @@ -165,11 +165,9 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe return; //sometimes oddly this happens, nothing we can do } - if (!response.status && response.message && response.stack) { - //this is a JS/angular error that we should deal with - return $q.reject({ - errorMsg: response.message - }); + if (!response.status) { + //this is a JS/angular error + return $q.reject(response); } //invoke the callback @@ -278,7 +276,11 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe //the data returned is the up-to-date data so the UI will refresh return $q.resolve(response.data); }, function (response) { - //failure callback + + if (!response.status) { + //this is a JS/angular error + return $q.reject(response); + } //when there's a 500 (unhandled) error show a YSOD overlay if debugging is enabled. if (response.status >= 500 && response.status < 600) { diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 889ebbd842..d99573decb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -202,6 +202,18 @@ @import "components/umbemailmarketing.less"; +// Property Editors +@import "../views/components/blockcard/umb-block-card-grid.less"; +@import "../views/components/blockcard/umb-block-card.less"; +@import "../views/propertyeditors/blocklist/umb-block-list-property-editor.less"; +@import "../views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less"; +@import "../views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less"; +@import "../views/propertyeditors/notsupported/notsupported.less"; +@import "../views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less"; +@import "../views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less"; +@import "../views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less"; + + // Utilities @import "utilities/layout/_display.less"; @import "utilities/theme/_opacity.less"; @@ -224,6 +236,7 @@ // Used for prevalue editors @import "components/prevalues/multivalues.less"; +@import "../views/prevalueeditors/numberrange.less"; // Dashboards @import "dashboards/getstarted.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 68854e3c53..017468fa0c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -111,7 +111,7 @@ } .umb-card-grid li { - overflow: hidden; + font-size: 12px; text-align: center; box-sizing: border-box; @@ -163,11 +163,23 @@ } -.umb-card-grid .umb-card-grid-item:hover, -.umb-card-grid .umb-card-grid-item:focus { +.umb-card-grid .umb-card-grid-item:hover { background-color: @ui-option-hover; color: @ui-option-type-hover; } +.umb-card-grid .umb-card-grid-item:focus { + color: @ui-option-type-hover; +} + +.umb-card-grid .umb-card-grid-item.--creator { + > span { + border: 2px dashed @ui-action-discreet-border; + border-radius: @baseBorderRadius; + &:hover { + border-color: @ui-action-discreet-border-hover; + } + } +} .umb-card-grid .umb-card-grid-item-slot { position: relative; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less index 15f85e1c77..76c0c55fca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/html/umb-group-panel.less @@ -8,11 +8,11 @@ .umb-group-panel__header { padding: 12px 20px; font-weight: bold; - font-size: 16px; + font-size: 14px; display: flex; align-items: center; justify-content: space-between; - color: @grayDark; + color: @grayDarker; border-bottom: 1px solid @gray-9; } 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 5868408969..035bf02f91 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -130,8 +130,12 @@ visibility: hidden; border-radius: @baseBorderRadius; + &.umb-overlay--small { + width: 420px; + } + &.umb-overlay--medium { - width: 480px; + width: 520px; } } 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 bb0b1ee3fb..315436591a 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 @@ -20,12 +20,18 @@ justify-content: center; height: calc(~'@{editorHeaderHeight}'- ~'1px'); // need to offset the 1px border-bottom on .umb-editor-header - avoids overflowing top of the container color: @ui-active-type; + user-select: none; &:hover { color: @ui-active-type-hover !important; text-decoration: none; } + &:disabled { + pointer-events: none; + color: @gray-6; + } + &::before { content: ""; position: absolute; 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 3509a4789f..cff9980483 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 @@ -41,6 +41,9 @@ &:checked ~ .umb-form-check__state .umb-form-check__check { border-color: @ui-option-type; } + &[type='checkbox']:checked ~ .umb-form-check__state .umb-form-check__check { + background-color: @ui-option-type; + } &:checked:hover ~ .umb-form-check__state .umb-form-check__check { &::before { background: @ui-option-type-hover; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index e7dc438747..384d9b4ac1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -38,6 +38,15 @@ position: relative; text-align: left; background: @white; + border: 1px solid @gray-9; + border-radius: @baseBorderRadius; + transition: border-color 120ms; + margin-bottom: 4px; + margin-top: 4px; + + &.--error { + border-color: @formErrorBorder !important; + } } .umb-nested-content__item.ui-sortable-placeholder { @@ -54,7 +63,7 @@ } .umb-nested-content__header-bar { - border-bottom: 1px solid @gray-9; + cursor: pointer; background-color: @white; @@ -207,9 +216,9 @@ .umb-nested-content__content { border-top: 1px solid transparent; - border-bottom: 1px solid @gray-9; - border-left: 1px solid @gray-9; - border-right: 1px solid @gray-9; + border-bottom: 1px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid transparent; border-radius: 0 0 3px 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 99f149f808..da46e51b9e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -151,7 +151,7 @@ h6.-black { } } -.umb-property:last-of-type .umb-control-group { +umb-property:last-of-type .umb-control-group { &::after { margin-top: 0px; height: 0; 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 49aa453de3..664be1dafc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -320,6 +320,11 @@ box-shadow:none !important; } +.umb-sortable-thumbnails-container { + display: flex; + flex-wrap: wrap; + background-color: @white; +} .umb-sortable-thumbnails { list-style-type: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 7f31369f3a..3f3ed2e376 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -199,11 +199,13 @@ @ui-btn-positive-type: @white; @ui-btn-negative: @red; +@ui-btn-negative-type: @white; @ui-btn-negative-hover: @red; @ui-icon: @blueNight; @ui-icon-hover: @blueMid; +@ui-drop-area-color: @blueMidLight; // Scaffolding @@ -254,6 +256,7 @@ // Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. @baseBorderRadius: 3px; // 2px; +@doubleBorderRadius: 6px; @borderRadiusLarge: 3px; // 6px; @borderRadiusSmall: 3px; // 3px; @@ -271,8 +274,8 @@ @btnBackgroundHighlight: @gray-10; @btnBorder: @gray-9; -@btnPrimaryBackground: @ui-btn-positive; -@btnPrimaryBackgroundHighlight: @ui-btn-positive-hover; +@btnPrimaryBackground: @ui-btn; +@btnPrimaryBackgroundHighlight: @ui-btn-hover; @btnInfoType: @ui-btn-type;// updated 2019 @btnInfoTypeHover: @ui-btn-type;// updated 2019 diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 297d93f4bc..d21331f106 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -31,13 +31,17 @@ function MainController($scope, $location, appState, treeService, notificationsS // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 function handleFirstTab(evt) { if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); + enableTabbingActive(); } } + function enableTabbingActive() { + $scope.tabbingActive = true; + $scope.$digest(); + window.addEventListener('mousedown', disableTabbingActive); + window.removeEventListener("keydown", handleFirstTab); + } + function disableTabbingActive(evt) { $scope.tabbingActive = false; $scope.$digest(); @@ -47,6 +51,12 @@ function MainController($scope, $location, appState, treeService, notificationsS window.addEventListener("keydown", handleFirstTab); + $scope.$on("showFocusOutline", function() { + $scope.tabbingActive = true; + window.addEventListener('mousedown', disableTabbingActive); + window.removeEventListener("keydown", handleFirstTab); + }); + $scope.removeNotification = function (index) { notificationsService.remove(index); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.content.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.content.html new file mode 100644 index 0000000000..15c3b9594f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.content.html @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js new file mode 100644 index 0000000000..f515cbb4ba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.controller.js @@ -0,0 +1,76 @@ +angular.module("umbraco") + .controller("Umbraco.Editors.BlockEditorController", + function ($scope, localizationService, formHelper) { + var vm = this; + + vm.model = $scope.model; + vm.tabs = []; + + localizationService.localizeMany([ + vm.model.liveEditing ? "prompt_discardChanges" : "general_close", + vm.model.liveEditing ? "buttons_confirmActionConfirm" : "buttons_submitChanges" + ]).then(function (data) { + vm.closeLabel = data[0]; + vm.submitLabel = data[1]; + }); + + if (vm.model.content && vm.model.content.variants) { + + var apps = vm.model.content.apps; + + // configure the content app based on settings + var contentApp = apps.find(entry => entry.alias === "umbContent"); + if (contentApp) { + if (vm.model.hideContent) { + apps.splice(apps.indexOf(contentApp), 1); + } else if (vm.model.openSettings !== true) { + contentApp.active = true; + } + } + + if (vm.model.settings && vm.model.settings.variants) { + var settingsApp = apps.find(entry => entry.alias === "settings"); + if (settingsApp) { + if (vm.model.openSettings) { + settingsApp.active = true; + } + } + } + + vm.tabs = apps; + } + + vm.submitAndClose = function () { + if (vm.model && vm.model.submit) { + // always keep server validations since this will be a nested editor and server validations are global + if (formHelper.submitForm({ + scope: $scope, + formCtrl: vm.blockForm, + keepServerValidation: true + })) { + vm.model.submit(vm.model); + } + } + } + + vm.close = function () { + if (vm.model && vm.model.close) { + // TODO: At this stage there could very well have been server errors that have been cleared + // but if we 'close' we are basically cancelling the value changes which means we'd want to cancel + // all of the server errors just cleared. It would be possible to do that but also quite annoying. + // The rudimentary way would be to: + // * Track all cleared server errors here by subscribing to the prefix validation of controls contained here + // * If this is closed, re-add all of those server validation errors + // A more robust way to do this would be to: + // * Add functionality to the serverValidationManager whereby we can remove validation errors and it will + // maintain a copy of the original errors + // * It would have a 'commit' method to commit the removed errors - which we would call in the formHelper.submitForm when it's successful + // * It would have a 'rollback' method to reset the removed errors - which we would call here + + // TODO: check if content/settings has changed and ask user if they are sure. + vm.model.close(vm.model); + } + } + + } + ); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.html new file mode 100644 index 0000000000..de18f13d2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.html @@ -0,0 +1,57 @@ +
+ + + + + + + + +
+ + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html new file mode 100644 index 0000000000..df69e2e648 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockeditor/blockeditor.settings.html @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js new file mode 100644 index 0000000000..2894e0bef4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.controller.js @@ -0,0 +1,60 @@ +angular.module("umbraco") +.controller("Umbraco.Editors.BlockPickerController", + function ($scope, localizationService) { + var vm = this; + + + vm.navigation = []; + + + localizationService.localizeMany(["blockEditor_tabCreateEmpty", "blockEditor_tabClipboard"]).then( + function (data) { + + vm.navigation = [{ + "alias": "empty", + "name": data[0], + "icon": "icon-add", + "active": true, + "view": "" + }, + { + "alias": "clipboard", + "name": data[1], + "icon": "icon-paste-in", + "view": "", + "disabled": vm.model.clipboardItems.length === 0 + }]; + + vm.activeTab = vm.navigation[0]; + } + ); + + + vm.onNavigationChanged = function(tab) { + vm.activeTab.active = false; + vm.activeTab = tab; + vm.activeTab.active = true; + } + + vm.clickClearClipboard = function() { + vm.onNavigationChanged(vm.navigation[0]); + vm.navigation[1].disabled = true;// disabled ws determined when creating the navigation, so we need to update it here. + vm.model.clipboardItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. + vm.model.clickClearClipboard(); + } + + vm.model = $scope.model; + + vm.selectItem = function(item, $event) { + vm.model.selectedItem = item; + vm.model.submit($scope.model, $event); + } + + vm.close = function() { + if ($scope.model && $scope.model.close) { + $scope.model.close($scope.model); + } + } + + } +); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html new file mode 100644 index 0000000000..fb7e946ee7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/blockpicker/blockpicker.html @@ -0,0 +1,81 @@ +
+ + + + + + +
+ +
+ + + +
+ + + +
+
+ +
+
+ + +
+ +
+ + + +
+
+ +
+ + + + + + + + + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index ad0aaab57c..65148d5cc6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -76,7 +76,8 @@ + select-result-callback="selectResult" + empty-search-result-position="default">
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html index 79dd6bd4f6..f35c24e96a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/confirm/confirm.html @@ -1,13 +1,13 @@ 
+

+
- {{model.confirmMessage}} + 'umb-alert--info': model.confirmMessageStyle === 'info'}" + ng-bind-html="model.confirmMessage">
-

{{model.content}}

-
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html index 25e3b44c85..405ca1d470 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.html @@ -41,6 +41,14 @@ +
  • + +
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less new file mode 100644 index 0000000000..e4953999fd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card-grid.less @@ -0,0 +1,15 @@ +.umb-block-card-grid { + /* FlexBox Fallback */ + display: flex; + flex-wrap: wrap; + > * { + flex: 1 1 240px; + } + + /* Grid Setup */ + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + grid-auto-rows: minmax(200px, auto); + grid-gap: 20px; + +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html new file mode 100644 index 0000000000..486bcbda4a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.html @@ -0,0 +1,11 @@ + +
    + +
    +
    +
    +
    +
    + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less new file mode 100644 index 0000000000..d1cde628e4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umb-block-card.less @@ -0,0 +1,129 @@ +.umb-block-card, +umb-block-card { + position: relative; + display: inline-block; + width: 100%; + height: auto; + background-color: white; + border-radius: @doubleBorderRadius; + box-shadow: 0 1px 2px rgba(0,0,0,.2); + overflow: hidden; + + transition: box-shadow 120ms; + + cursor: pointer; + + &:hover { + box-shadow: 0 1px 3px rgba(@ui-action-type-hover, .5); + } + + &.--isOpen { + &::after { + content: ""; + position: absolute; + border: 2px solid @ui-active-border; + border-radius: @doubleBorderRadius; + top:0; + bottom: 0; + left: 0; + right: 0; + } + } + + &.--sortable-placeholder { + &::after { + content: ""; + position: absolute; + background-color:rgba(@ui-drop-area-color, .05); + border: 2px solid rgba(@ui-drop-area-color, .1); + border-radius: @doubleBorderRadius; + box-shadow: 0 0 4px rgba(@ui-drop-area-color, 0.05); + top:0; + bottom: 0; + left: 0; + right: 0; + animation: umb-block-card--sortable-placeholder 400ms ease-in-out alternate infinite; + @keyframes umb-block-card--sortable-placeholder { + 0% { opacity: 1; } + 100% { opacity: 0.5; } + } + } + box-shadow: none; + } + + .__showcase { + position: relative; + width: 100%; + padding-bottom: 10/16*100%; + background-color: @gray-12; + + background-size: cover; + background-position: 50% 50%; + background-repeat: no-repeat; + + .__icon { + position: absolute; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + font-size:42px; + } + } + + .__info { + width: 100%; + background-color: #fff; + padding-bottom: 6px; + + .__name { + font-weight: bold; + font-size: 14px; + color: @ui-action-type; + margin-left: 16px; + margin-top: 8px; + margin-bottom: -1px; + } + .__subname { + color: @gray-4; + font-size: 12px; + margin-left: 16px; + margin-bottom: -1px; + } + } + + &:hover { + .__info { + .__name { + color: @ui-action-type-hover; + } + } + } + + .__actions { + position: absolute; + top: 10px; + right: 0; + opacity: 0; + transition: opacity 120ms; + .__action { + display: inline-block; + border-radius: 50%; + width: 28px; + height: 28px; + margin-right: 10px; + background-color: white; + color:@ui-action-type; + &:hover { + color: @ui-action-type-hover; + } + } + } + &:hover, &:focus, &:focus-within { + .__actions { + opacity: 1; + } + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js new file mode 100644 index 0000000000..41ae035617 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/blockcard/umbBlockCard.component.js @@ -0,0 +1,23 @@ +(function () { + "use strict"; + + angular + .module("umbraco") + .component("umbBlockCard", { + templateUrl: "views/components/blockcard/umb-block-card.html", + controller: BlockCardController, + controllerAs: "vm", + transclude: true, + bindings: { + blockConfigModel: "<", + elementTypeModel: "<" + } + }); + + function BlockCardController() { + + var vm = this; + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html index d743907d07..484e0175c5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation-item.html @@ -4,6 +4,7 @@ hotkey="{{::vm.hotkey}}" hotkey-when-hidden="true" ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}" + ng-disabled="vm.item.disabled" class="umb-sub-views-nav-item__action umb-outline umb-outline--thin"> {{ vm.item.name }} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-view.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-view.html index 2cddbe0967..807c8c7ebc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-view.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-view.html @@ -1,6 +1,6 @@
    + val-sub-view="model">
    + val-sub-view="subView">
    -
    + +
    -
    + +
    +
    -
    + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html new file mode 100644 index 0000000000..fae639562f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umb-element-editor-content.component.html @@ -0,0 +1,39 @@ +
    + +
    + +
    +
    {{ group.label }}
    +
    + +
    + + +
    + + +
    + +
    +
    + +
    + + + + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js new file mode 100644 index 0000000000..b4a650a0c5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/elementeditor/umbelementeditorcontent.component.js @@ -0,0 +1,29 @@ +(function () { + 'use strict'; + + // TODO: Add docs - this component is used to render a content item based on an Element Type as a nested editor + + angular + .module('umbraco.directives') + .component('umbElementEditorContent', { + templateUrl: 'views/components/elementeditor/umb-element-editor-content.component.html', + controller: ElementEditorContentComponentController, + controllerAs: 'vm', + bindings: { + model: '=' + } + }); + + function ElementEditorContentComponentController($scope) { + + // We need a controller for the component to work. + var vm = this; + + vm.getScope = getScope;// used by property editors to get a scope that is the root of split view, content apps etc. + function getScope() { + return $scope; + } + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 658b7c73ab..14ca023046 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,29 +1,30 @@
    -
    +
    -
    - - {{inheritsFrom}} +
    + + {{vm.inheritsFrom}} -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html index b5eedaa24f..2a476c0500 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/tree/umb-tree-search-results.html @@ -1,5 +1,5 @@
    - + 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 f0d8eafdde..f8a7b005cf 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 @@ -4,7 +4,7 @@ function PublishDescendantsController($scope, localizationService) { var vm = this; - vm.includeUnpublished = false; + vm.includeUnpublished = $scope.model.includeUnpublished || false; vm.changeSelection = changeSelection; vm.toggleIncludeUnpublished = toggleIncludeUnpublished; @@ -12,14 +12,20 @@ function onInit() { 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.displayVariants = vm.variants.slice(0); // shallow copy, we don't 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 = {}; + // get localized texts for use in directives if (!$scope.model.title) { localizationService.localize("buttons_publishDescendants").then(value => { $scope.model.title = value; }); } + if (!vm.labels.includeUnpublished) { + localizationService.localize("content_includeUnpublished").then(value => { + vm.labels.includeUnpublished = value; + }); + } vm.variants.forEach(variant => { variant.isMandatory = isMandatoryFilter(variant); @@ -67,6 +73,8 @@ function toggleIncludeUnpublished() { vm.includeUnpublished = !vm.includeUnpublished; + // make sure this value is pushed back to the scope + $scope.model.includeUnpublished = vm.includeUnpublished; } /** Returns true if publishing is possible based on if there are un-published mandatory languages */ 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 d5a4cd5d81..14dcc3a760 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 @@ -6,15 +6,15 @@
    - + -
    @@ -26,15 +26,14 @@
    - + -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html b/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html index 846a53c5ea..452248b3ea 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/dictionary/edit.html @@ -23,7 +23,7 @@

    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 88230493b9..cb21a5ecce 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 @@ -359,6 +359,7 @@ if (infiniteMode && $scope.model.submit) { $scope.model.documentTypeAlias = vm.contentType.alias; + $scope.model.documentTypeKey = vm.contentType.key; $scope.model.submit($scope.model); } diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/numberrange.html b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/numberrange.html new file mode 100644 index 0000000000..d9d8cad982 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/numberrange.html @@ -0,0 +1,24 @@ +
    + + + + + + + Not a number + + + Not a number + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/numberrange.less b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/numberrange.less new file mode 100644 index 0000000000..e7ca2dd1cd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/numberrange.less @@ -0,0 +1,14 @@ +.umb-prevalues-numberrange { + + .__min-input { + } + + .__enDash { + display: inline-block; + vertical-align: middle; + margin-top: 5px; + } + + .__max-input { + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.html new file mode 100644 index 0000000000..8c3bced573 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.html @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.controller.js new file mode 100644 index 0000000000..c2c86ca824 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.controller.js @@ -0,0 +1,27 @@ +(function () { + 'use strict'; + + function InlineBlockEditor($scope) { + + const bc = this; + + bc.openBlock = function(block) { + + // if we are closing: + if (block.active === true) { + // boardcast the formSubmitting event to trigger syncronization or none-live property-editors + $scope.$broadcast("formSubmitting", { scope: $scope }); + // Some property editors need to performe an action after all property editors have reacted to the formSubmitting. + $scope.$broadcast("postFormSubmitting", { scope: $scope }); + + block.active = false; + } else { + $scope.api.activateBlock(block); + } + } + + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor", InlineBlockEditor); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html new file mode 100644 index 0000000000..360eeed8c0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html @@ -0,0 +1,14 @@ +
    + +
    + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less new file mode 100644 index 0000000000..2be20946b8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.less @@ -0,0 +1,84 @@ +.blockelement-inlineblock-editor { + display: block; + margin-bottom: 4px; + margin-top: 4px; + border: 1px solid @gray-9; + border-radius: @baseBorderRadius; + transition: border-color 120ms; + + .umb-block-list__block:not(.--active) &:hover { + border-color: @gray-8; + } + + > button { + width: 100%; + min-height: 48px; + cursor: pointer; + color: @ui-action-discreet-type; + text-align: left; + padding-left: 10px; + padding-bottom: 2px; + user-select: none; + background-color: white; + + .caret { + transform: rotate(-90deg); + transition: transform 80ms ease-out; + } + + i { + font-size: 22px; + display: inline-block; + vertical-align: middle; + } + + span { + display: inline-block; + vertical-align: middle; + } + + &:hover { + color: @ui-action-discreet-type-hover; + border-color: @gray-8; + } + } + + .umb-block-list__block.--active & { + border-color: @gray-8; + box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.05); + + > button { + > .caret { + transform: rotate(0deg); + } + } + } + + &.--error { + border-color: @formErrorBorder !important; + } +} +.blockelement-inlineblock-editor__inner { + border-top: 1px solid @gray-8; + background-color: @gray-12; + + > * > * > * > .umb-group-panel { + background-color: transparent; + box-shadow: none; + margin-top: 10px; + margin-bottom: 0; + > .umb-group-panel__content .umb-property { + margin-bottom: 20px; + } + } + .umb-group-panel + .umb-group-panel { + margin-top: 20px; + } + &.--singleGroup > * > * > * > .umb-group-panel { + margin-top: 0; + > .umb-group-panel__header { + display: none; + } + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html new file mode 100644 index 0000000000..4aacc32c62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html @@ -0,0 +1,8 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less new file mode 100644 index 0000000000..51fb7242ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.less @@ -0,0 +1,45 @@ +.blockelement-labelblock-editor { + position: relative; + display: block; + margin-bottom: 4px; + margin-top: 4px; + width: 100%; + min-height: 48px; + border: 1px solid @gray-9; + border-radius: @baseBorderRadius; + cursor: pointer; + color: @ui-action-discreet-type; + background-color: white; + text-align: left; + padding-left: 20px; + padding-bottom: 2px; + user-select: none; + transition: border-color 120ms; + + i { + font-size: 22px; + margin-right: 5px; + display: inline-block; + vertical-align: middle; + } + + span { + display: inline-block; + vertical-align: middle; + } + + &:hover { + color: @ui-action-discreet-type-hover; + background-color: @ui-action-hover; + } + + &.--active { + color: @ui-active-type; + border-color: @ui-active; + background-color: @ui-active; + } + + &.--error { + border-color: @formErrorBorder !important; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html new file mode 100644 index 0000000000..84d5dd17b7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html @@ -0,0 +1,13 @@ +
    +
    + + {{block.label}} +
    +
    + This Block is no longer supported in this context.
    + You might want to remove this block, or contact your developer to take actions for making this block available again.

    + Learn about this circumstance +
    Block data:
    +
    
    +    
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less new file mode 100644 index 0000000000..aff741e278 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.less @@ -0,0 +1,58 @@ +.blockelement-unsupportedblock-editor { + + position: relative; + display: block; + box-sizing: border-box; + margin-bottom: 4px; + margin-top: 4px; + width: 100%; + border: 1px solid @gray-9; + border-radius: @baseBorderRadius; + + > .__header { + + display: flex; + align-items: center; + + padding-left: 20px; + padding-bottom: 2px; + min-height: 48px; + border-bottom: 1px solid @gray-9; + + background-color: @gray-11; + color: @gray-6; + + i { + font-size: 22px; + margin-right: 5px; + display: inline-block; + vertical-align: middle; + } + span { + display: inline-block; + vertical-align: middle; + } + } + + > .__body { + + padding: 20px; + + background-color: @gray-11; + + a { + text-decoration: underline; + color: @ui-action-type; + &:hover { + color:@ui-action-type-hover; + } + } + + pre { + border: none; + padding: 0; + background-color: transparent; + } + + } +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js new file mode 100644 index 0000000000..dd9fc7f83d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.controller.js @@ -0,0 +1,219 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.BlockList.BlockConfigurationController + * @function + * + * @description + * The controller for the content type editor property settings dialog + */ + +(function () { + "use strict"; + + function TransferProperties(fromObject, toObject) { + for (var p in fromObject) { + toObject[p] = fromObject[p]; + } + } + + function BlockConfigurationController($scope, elementTypeResource, overlayService, localizationService, editorService, eventsService) { + + var unsubscribe = []; + + var vm = this; + vm.openBlock = null; + + function onInit() { + + if (!$scope.model.value) { + $scope.model.value = []; + } + + loadElementTypes(); + + } + + function loadElementTypes() { + return elementTypeResource.getAll().then(function (elementTypes) { + vm.elementTypes = elementTypes; + }); + } + + function updateUsedElementTypes(event, args) { + var key = args.documentType.key; + for (var i = 0; i { + obj.alias = vm.getElementTypeByKey(obj.contentTypeKey).alias; + return obj; + }); + + var availableItems = vm.getAvailableElementTypes() + + localizationService.localizeMany(["blockEditor_headlineCreateBlock", "blockEditor_labelcreateNewElementType"]).then(function(localized) { + + var elemTypeSelectorOverlay = { + view: "itempicker", + title: localized[0], + availableItems: availableItems, + selectedItems: selectedItems, + createNewItem: { + action: function() { + overlayService.close(); + vm.createElementTypeAndCallback(vm.addBlockFromElementTypeKey); + }, + icon: "icon-add", + name: localized[1] + }, + position: "target", + event: $event, + size: availableItems.length < 7 ? "small" : "medium", + submit: function (overlay) { + vm.addBlockFromElementTypeKey(overlay.selectedItem.key); + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(elemTypeSelectorOverlay); + + }); + }; + + vm.createElementTypeAndCallback = function(callback) { + const editor = { + create: true, + infiniteMode: true, + isElement: true, + submit: function (model) { + loadElementTypes().then( function () { + callback(model.documentTypeKey); + }); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.documentTypeEditor(editor); + } + + vm.addBlockFromElementTypeKey = function(key) { + + var entry = { + "contentTypeKey": key, + "settingsElementTypeKey": null, + "labelTemplate": "", + "view": null, + "stylesheet": null, + "editorSize": "medium", + "iconColor": null, + "backgroundColor": null, + "thumbnail": null + }; + + $scope.model.value.push(entry); + }; + + + + + + vm.openBlockOverlay = function (block) { + + localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [vm.getElementTypeByKey(block.contentTypeKey).name]).then(function (data) { + + var clonedBlockData = Utilities.copy(block); + vm.openBlock = block; + + var overlayModel = { + block: clonedBlockData, + title: data, + view: "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html", + size: "small", + submit: function(overlayModel) { + loadElementTypes()// lets load elementType again, to ensure we are up to date. + TransferProperties(overlayModel.block, block);// transfer properties back to block object. (Doing this cause we dont know if block object is added to model jet, therefor we cant use index or replace the object.) + overlayModel.close(); + }, + close: function() { + editorService.close(); + vm.openBlock = null; + } + }; + + // open property settings editor + editorService.open(overlayModel); + + }); + + }; + + $scope.$on('$destroy', function () { + unsubscribe.forEach(u => { u(); }); + }); + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockList.BlockConfigurationController", BlockConfigurationController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html new file mode 100644 index 0000000000..41dea86131 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html @@ -0,0 +1,30 @@ +
    + +
    + + +
    + +
    +
    + + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less new file mode 100644 index 0000000000..f4d9caa73b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.less @@ -0,0 +1,28 @@ +.umb-block-list-block-configuration { + + .__add-button { + position: relative; + display: inline-flex; + width: 100%; + height: auto; + margin-right: 20px; + margin-bottom: 20px; + + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + border-radius: @doubleBorderRadius; + + align-items: center; + justify-content: center; + + padding: 5px 15px; + box-sizing: border-box; + font-weight: bold; + } + + .__add-button:hover { + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js new file mode 100644 index 0000000000..0f58b84ee9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js @@ -0,0 +1,292 @@ +/** + * @ngdoc controller + * @name Umbraco.Editors.BlockList.BlockConfigurationOverlayController + * @function + * + * @description + * The controller for the content type editor property settings dialog + */ + +(function () { + "use strict"; + + function BlockConfigurationOverlayController($scope, overlayService, localizationService, editorService, elementTypeResource, eventsService) { + + var unsubscribe = []; + + var vm = this; + vm.block = $scope.model.block; + + loadElementTypes(); + + function loadElementTypes() { + return elementTypeResource.getAll().then(function (elementTypes) { + vm.elementTypes = elementTypes; + + vm.contentPreview = vm.getElementTypeByKey(vm.block.contentTypeKey); + vm.settingsPreview = vm.getElementTypeByKey(vm.block.settingsElementTypeKey); + }); + } + + vm.getElementTypeByKey = function(key) { + return vm.elementTypes.find(function (type) { + return type.key === key; + }); + }; + + vm.openElementType = function(elementTypeKey) { + var elementTypeId = vm.getElementTypeByKey(elementTypeKey).id; + const editor = { + id: elementTypeId, + submit: function (model) { + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.documentTypeEditor(editor); + } + + vm.createElementTypeAndCallback = function(callback) { + const editor = { + create: true, + infiniteMode: true, + isElement: true, + submit: function (model) { + callback(model.documentTypeKey); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.documentTypeEditor(editor); + } + + vm.addSettingsForBlock = function ($event, block) { + + localizationService.localizeMany(["blockEditor_headlineAddSettingsElementType", "blockEditor_labelcreateNewElementType"]).then(function(localized) { + + var elemTypeSelectorOverlay = { + view: "itempicker", + title: localized[0], + availableItems: vm.elementTypes, + position: "target", + event: $event, + size: vm.elementTypes.length < 7 ? "small" : "medium", + createNewItem: { + action: function() { + overlayService.close(); + vm.createElementTypeAndCallback((key) => { + vm.applySettingsToBlock(block, key); + }); + }, + icon: "icon-add", + name: localized[1] + }, + submit: function (overlay) { + vm.applySettingsToBlock(block, overlay.selectedItem.key); + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(elemTypeSelectorOverlay); + + }); + }; + vm.applySettingsToBlock = function(block, key) { + block.settingsElementTypeKey = key; + }; + + vm.requestRemoveSettingsForBlock = function(block) { + localizationService.localizeMany(["general_remove", "defaultdialogs_confirmremoveusageof"]).then(function (data) { + + var settingsElementType = vm.getElementTypeByKey(block.settingsElementTypeKey); + + overlayService.confirmRemove({ + title: data[0], + content: localizationService.tokenReplace(data[1], [settingsElementType.name]), + close: function () { + overlayService.close(); + }, + submit: function () { + vm.removeSettingsForBlock(block); + overlayService.close(); + } + }); + }); + }; + vm.removeSettingsForBlock = function(block) { + block.settingsElementTypeKey = null; + }; + + + function updateUsedElementTypes(event, args) { + var key = args.documentType.key; + for (var i = 0; i { u(); }); + }); + + } + + angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockList.BlockConfigurationOverlayController", BlockConfigurationOverlayController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html new file mode 100644 index 0000000000..9675677c11 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.html @@ -0,0 +1,248 @@ +
    + +
    + + + + + + + +
    + +
    + +
    + Editor appearance +
    + +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    +
    + +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    + +
    + Data Models +
    + +
    + + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + + +
    +
    + +
    +
    + +
    + + +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    + Catalogue appearance +
    + +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    + +
    + Advanced +
    + +
    + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    + + + + + + + + + + + + + + +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less new file mode 100644 index 0000000000..8d063c77fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.less @@ -0,0 +1,100 @@ +.umb-block-list-block-configuration-overlay { + + + .umb-node-preview { + flex-grow: 1; + } + + .__control-actions { + position: absolute; + display: flex; + align-items: center; + top:0; + bottom: 0; + right: 0; + background-color: rgba(255, 255, 255, 0.8); + opacity: 0; + transition: opacity 120ms; + } + .control-group:hover, + .control-group:focus, + .control-group:focus-within { + .__control-actions { + opacity: 1; + } + } + .__control-actions-btn { + position: relative; + color: @ui-action-discreet-type; + height: 32px; + width: 26px; + &:hover { + color: @ui-action-discreet-type-hover; + } + &:last-of-type { + margin-right: 7px; + } + } + + .umb-node-preview { + border-bottom: none; + } + + .__settings-input { + position: relative; + padding: 5px 8px; + margin-bottom: 10px; + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + width: 100%; + font-weight: bold; + display: inline-flex; + flex-flow: row nowrap; + + localize { + width: 100%; + } + + .umb-node-preview { + padding: 3px 0; + margin-left: 5px; + overflow: hidden; + } + + &.--noValue { + text-align: center; + border-radius: @baseBorderRadius; + color: white; + transition: color 120ms; + &:hover, &:focus { + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + } + } + + &.--hasValue { + border: 1px solid @inputBorder; + padding: 0; + } + } + + .__add-button { + width:100%; + color: @ui-action-discreet-type; + border: 1px dashed @ui-action-discreet-border; + border-radius: @baseBorderRadius; + display: flex; + align-items: center; + justify-content: center; + padding: 5px 15px; + box-sizing: border-box; + margin: 20px 0; + font-weight: bold; + } + + .__add-button:hover { + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html new file mode 100644 index 0000000000..aa16e6e9a8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.html @@ -0,0 +1,63 @@ +
    + + + +
    + +
    + +
    + + + + + + +
    +
    + + + + + + +
    +
    + Minimum %0% entries, needs %1% more. +
    + > +
    +
    +
    + Maximum %0% entries, %1% too many. +
    + +
    + +
    + + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less new file mode 100644 index 0000000000..b6f3ace44c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-property-editor.less @@ -0,0 +1,174 @@ +@umb-block-list__item_minimum_height: 48px; + +.umb-block-list { + padding-bottom:10px; +} + +.umb-block-list .umb-load-indicator { + margin-bottom:20px; +} + +.umb-block-list__wrapper { + position: relative; + max-width: 1024px; + > .ui-sortable > .ui-sortable-helper > .umb-block-list__block > .umb-block-list__block--content > * { + box-shadow: 0px 5px 10px 0 rgba(0,0,0,.2); + } +} + +.umb-block-list__block { + position: relative; + width: 100%; + + > .umb-block-list__block--actions { + opacity: 0; + transition: opacity 120ms; + + .--error { + color: @formErrorBorder !important; + } + } + + &:hover, + &:focus, + &:focus-within, + &.--active { + + > .umb-block-list__block--actions { + opacity: 1; + } + } +} +.umb-block-list__block--actions { + position: absolute; + z-index:999999999;// We always want to be on top of custom view, but we need to make sure we still are behind relevant Umbraco CMS UI. ToDo: Needs further testing. + top: 10px; + right: 10px; + font-size: 0; + background-color: rgba(255, 255, 255, .96); + border-radius: 16px; + padding-left: 5px; + padding-right: 5px; + .action { + position: relative; + display: inline-block; + color: @ui-action-discreet-type; + font-size: 18px; + padding: 5px; + &:hover { + color: @ui-action-discreet-type-hover; + } + } +} + +.umb-block-list__block--content { + position: relative; + width: 100%; + min-height: @umb-block-list__item_minimum_height; + background-color: @white; + border-radius: @baseBorderRadius; +} + +.blockelement__draggable-element { + cursor: grab; +} + + +.umb-block-list__block--create-button { + position: absolute; + width: 100%; + z-index:1; + opacity: 0; + outline: none; + height: 12px; + margin-top: -9px; + padding-top: 6px; + margin-bottom: -6px; + transition: opacity 240ms; + + &::before { + content: ''; + position: absolute; + background-color: @blueMid; + border-top:1px solid white; + border-bottom:1px solid white; + border-radius: 2px; + top:5px; + right: 0; + left: 0; + height: 2px; + animation: umb-block-list__block--create-button_before 400ms ease-in-out alternate infinite; + @keyframes umb-block-list__block--create-button_before { + 0% { opacity: 1; } + 100% { opacity: 0.5; } + } + } + > .__plus { + position: absolute; + pointer-events: none;// lets stop avoiding the mouse values in JS move event. + margin-left: -18px - 10px; + margin-top: -18px; + margin-bottom: -18px; + width: 28px; + height: 25px; + padding-bottom: 3px; + border-radius: 3em; + border: 2px solid @blueMid; + display: flex; + justify-content: center; + align-items: center; + color: @blueMid; + font-size: 20px; + font-weight: 800; + background-color: rgba(255, 255, 255, .96); + box-shadow: 0 0 0 2px rgba(255, 255, 255, .96); + transform: scale(0); + transition: transform 240ms ease-in; + animation: umb-block-list__block--create-button_after 800ms ease-in-out infinite; + @keyframes umb-block-list__block--create-button_after { + 0% { color: rgba(@blueMid, 0.8); } + 50% { color: rgba(@blueMid, 1); } + 100% { color: rgba(@blueMid, 0.8); } + } + } + &:focus { + > .__plus { + border: 2px solid @ui-outline; + } + } + &:hover, &:focus { + opacity: 1; + transition-duration: 120ms; + > .__plus { + transform: scale(1); + transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + } +} +.umb-block-list__create-button { + position: relative; + display: flex; + width: 100%; + align-items: center; + justify-content: center; + border: 1px dashed @ui-action-discreet-border; + color: @ui-action-discreet-type; + font-weight: bold; + margin: 2px 0; + padding: 5px 15px; + box-sizing: border-box; + border-radius: @baseBorderRadius; +} + +.umb-block-list__create-button:hover { + color: @ui-action-discreet-type-hover; + border-color: @ui-action-discreet-border-hover; + text-decoration: none; +} + +.umb-block-list__create-button.--disabled, +.umb-block-list__create-button.--disabled:hover { + color: @gray-7; + border-color: @gray-7; + cursor: default; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-row.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-row.html new file mode 100644 index 0000000000..55f849e4d0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umb-block-list-row.html @@ -0,0 +1,40 @@ + +
    + + + + +
    + + + +
    +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js new file mode 100644 index 0000000000..327677c6b9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js @@ -0,0 +1,615 @@ +(function () { + "use strict"; + + + /** + * @ngdoc directive + * @name umbraco.directives.directive:umbBlockListPropertyEditor + * @function + * + * @description + * The component for the block list property editor. + */ + angular + .module("umbraco") + .component("umbBlockListPropertyEditor", { + templateUrl: "views/propertyeditors/blocklist/umb-block-list-property-editor.html", + controller: BlockListController, + controllerAs: "vm", + bindings: { + model: "=" + }, + require: { + propertyForm: "^form", + umbProperty: "?^umbProperty", + umbVariantContent: '?^^umbVariantContent', + umbVariantContentEditors: '?^^umbVariantContentEditors', + umbElementEditorContent: '?^^umbElementEditorContent' + } + }); + + function BlockListController($scope, editorService, clipboardService, localizationService, overlayService, blockEditorService, udiService, serverValidationManager, angularHelper) { + + var unsubscribe = []; + var modelObject; + + // Property actions: + var copyAllBlocksAction; + var deleteAllBlocksAction; + + var inlineEditing = false; + var liveEditing = true; + + var vm = this; + + vm.loading = true; + vm.currentBlockInFocus = null; + vm.setBlockFocus = function(block) { + if(vm.currentBlockInFocus !== null) { + vm.currentBlockInFocus.focus = false; + } + vm.currentBlockInFocus = block; + block.focus = true; + } + vm.supportCopy = clipboardService.isSupported(); + + vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model. + vm.availableBlockTypes = []; // Available block entries of this property editor. + + var labels = {}; + vm.labels = labels; + localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) { + labels.grid_addElement = data[0]; + labels.content_createEmpty = data[1]; + }); + + vm.$onInit = function() { + if (!vm.umbVariantContent) { + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain($scope, s => s && s.vm && s.vm.constructor.name === "umbVariantContentController"); + vm.umbVariantContent = found ? found.vm : null; + if (!vm.umbVariantContent) { + throw "Could not find umbVariantContent in the $scope chain"; + } + } + + // set the onValueChanged callback, this will tell us if the block list model changed on the server + // once the data is submitted. If so we need to re-initialize + vm.model.onValueChanged = onServerValueChanged; + + inlineEditing = vm.model.config.useInlineEditingAsDefault; + liveEditing = vm.model.config.useLiveEditing; + + vm.validationLimit = vm.model.config.validationLimit; + + vm.listWrapperStyles = {}; + + if (vm.model.config.maxPropertyWidth) { + vm.listWrapperStyles['max-width'] = vm.model.config.maxPropertyWidth; + } + + // We need to ensure that the property model value is an object, this is needed for modelObject to recive a reference and keep that updated. + if(typeof vm.model.value !== 'object' || vm.model.value === null) {// testing if we have null or undefined value or if the value is set to another type than Object. + vm.model.value = {}; + } + + var scopeOfExistence = $scope; + if(vm.umbVariantContentEditors && vm.umbVariantContentEditors.getScope) { + scopeOfExistence = vm.umbVariantContentEditors.getScope(); + } else if(vm.umbElementEditorContent && vm.umbElementEditorContent.getScope) { + scopeOfExistence = vm.umbElementEditorContent.getScope(); + } + + // Create Model Object, to manage our data for this Block Editor. + modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, scopeOfExistence, $scope); + modelObject.load().then(onLoaded); + + copyAllBlocksAction = { + labelKey: "clipboard_labelForCopyAllEntries", + labelTokens: [vm.model.label], + icon: "documents", + method: requestCopyAllBlocks, + isDisabled: true + } + deleteAllBlocksAction = { + labelKey: 'clipboard_labelForRemoveAllEntries', + labelTokens: [], + icon: 'trash', + method: requestDeleteAllBlocks, + isDisabled: true + } + + var propertyActions = [ + copyAllBlocksAction, + deleteAllBlocksAction + ]; + + if (vm.umbProperty) { + vm.umbProperty.setPropertyActions(propertyActions); + } + }; + + // Called when we save the value, the server may return an updated data and our value is re-synced + // we need to deal with that here so that our model values are all in sync so we basically re-initialize. + function onServerValueChanged(newVal, oldVal) { + + // We need to ensure that the property model value is an object, this is needed for modelObject to recive a reference and keep that updated. + if (typeof newVal !== 'object' || newVal === null) {// testing if we have null or undefined value or if the value is set to another type than Object. + newVal = {}; + } + + modelObject.update(newVal, $scope); + onLoaded(); + } + + function setDirty() { + if (vm.propertyForm) { + vm.propertyForm.$setDirty(); + } + } + + function onLoaded() { + + // Store a reference to the layout model, because we need to maintain this model. + vm.layout = modelObject.getLayout([]); + + var invalidLayoutItems = []; + + // Append the blockObjects to our layout. + vm.layout.forEach(entry => { + // $block must have the data property to be a valid BlockObject, if not its considered as a destroyed blockObject. + if (entry.$block === undefined || entry.$block === null || entry.$block.data === undefined) { + var block = getBlockObject(entry); + + // If this entry was not supported by our property-editor it would return 'null'. + if (block !== null) { + entry.$block = block; + } + else { + // then we need to filter this out and also update the underlying model. This could happen if the data + // is invalid for some reason or the data structure has changed. + invalidLayoutItems.push(entry); + } + } + }); + + // remove the ones that are invalid + invalidLayoutItems.forEach(entry => { + var index = vm.layout.findIndex(x => x === entry); + if (index >= 0) { + vm.layout.splice(index, 1); + } + }); + + vm.availableContentTypesAliases = modelObject.getAvailableAliasesForBlockContent(); + vm.availableBlockTypes = modelObject.getAvailableBlocksForBlockPicker(); + + vm.loading = false; + + $scope.$evalAsync(); + + } + + function getDefaultViewForBlock(block) { + + if (block.config.unsupported === true) + return "views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html"; + + if (inlineEditing === true) + return "views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html"; + return "views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html"; + } + + function getBlockObject(entry) { + var block = modelObject.getBlockObject(entry); + + if (block === null) return null; + + // ensure that the containing content variant language/culture is transfered along + // to the scaffolded content object representing this block. This is required for validation + // along with ensuring that the umb-property inheritance is constently maintained. + if (vm.umbVariantContent.editor.content.language) { + block.content.language = vm.umbVariantContent.editor.content.language; + // currently we only ever deal with invariant content for blocks so there's only one + block.content.variants[0].tabs.forEach(tab => { + tab.properties.forEach(prop => { + prop.culture = vm.umbVariantContent.editor.content.language.culture; + }); + }); + } + + // TODO: Why is there a '/' prefixed? that means this will never work with virtual directories + block.view = (block.config.view ? "/" + block.config.view : getDefaultViewForBlock(block)); + + block.hideContentInOverlay = block.config.forceHideContentEditorInOverlay === true || inlineEditing === true; + block.showSettings = block.config.settingsElementTypeKey != null; + block.showCopy = vm.supportCopy && block.config.contentTypeKey != null;// if we have content, otherwise it doesn't make sense to copy. + + return block; + } + + + function addNewBlock(index, contentTypeKey) { + + // Create layout entry. (not added to property model jet.) + var layoutEntry = modelObject.create(contentTypeKey); + if (layoutEntry === null) { + return false; + } + + // make block model + var blockObject = getBlockObject(layoutEntry); + if (blockObject === null) { + return false; + } + + // If we reach this line, we are good to add the layoutEntry and blockObject to our models. + + // Add the Block Object to our layout entry. + layoutEntry.$block = blockObject; + + // add layout entry at the decired location in layout. + vm.layout.splice(index, 0, layoutEntry); + + // lets move focus to this new block. + vm.setBlockFocus(blockObject); + + return true; + + } + + function deleteBlock(block) { + + var layoutIndex = vm.layout.findIndex(entry => entry.contentUdi === block.content.udi); + if (layoutIndex === -1) { + throw new Error("Could not find layout entry of block with udi: "+block.content.udi) + } + + setDirty(); + + var removed = vm.layout.splice(layoutIndex, 1); + removed.forEach(x => { + // remove any server validation errors associated + var guid = udiService.getKey(x.contentUdi); + serverValidationManager.removePropertyError(guid, vm.umbProperty.property.culture, vm.umbProperty.property.segment, "", { matchType: "contains" }); + }); + + modelObject.removeDataAndDestroyModel(block); + + } + + function deleteAllBlocks() { + vm.layout.forEach(entry => { + deleteBlock(entry.$block); + }); + } + + function activateBlock(blockObject) { + blockObject.active = true; + } + + function editBlock(blockObject, openSettings, blockIndex, parentForm) { + + // this must be set + if (blockIndex === undefined) { + throw "blockIndex was not specified on call to editBlock"; + } + + var wasNotActiveBefore = blockObject.active !== true; + + // dont open the editor overlay if block has hidden its content editor in overlays and we are requesting to open content, not settings. + if (openSettings !== true && blockObject.hideContentInOverlay === true) { + return; + } + + // if requesting to open settings but we dont have settings then return. + if (openSettings === true && !blockObject.config.settingsElementTypeKey) { + return; + } + + activateBlock(blockObject); + + // make a clone to avoid editing model directly. + var blockContentClone = Utilities.copy(blockObject.content); + var blockSettingsClone = null; + + if (blockObject.config.settingsElementTypeKey) { + blockSettingsClone = Utilities.copy(blockObject.settings); + } + + var blockEditorModel = { + $parentScope: $scope, // pass in a $parentScope, this maintains the scope inheritance in infinite editing + $parentForm: parentForm || vm.propertyForm, // pass in a $parentForm, this maintains the FormController hierarchy with the infinite editing view (if it contains a form) + hideContent: blockObject.hideContentInOverlay, + openSettings: openSettings === true, + liveEditing: liveEditing, + title: blockObject.label, + index: blockIndex, + view: "views/common/infiniteeditors/blockeditor/blockeditor.html", + size: blockObject.config.editorSize || "medium", + submit: function(blockEditorModel) { + + if (liveEditing === false) { + // transfer values when submitting in none-liveediting mode. + blockObject.retrieveValuesFrom(blockEditorModel.content, blockEditorModel.settings); + } + + blockObject.active = false; + editorService.close(); + }, + close: function() { + + if (liveEditing === true) { + // revert values when closing in liveediting mode. + blockObject.retrieveValuesFrom(blockContentClone, blockSettingsClone); + } + + if (wasNotActiveBefore === true) { + blockObject.active = false; + } + editorService.close(); + } + }; + + if (liveEditing === true) { + blockEditorModel.content = blockObject.content; + blockEditorModel.settings = blockObject.settings; + } else { + blockEditorModel.content = blockContentClone; + blockEditorModel.settings = blockSettingsClone; + } + + // open property settings editor + editorService.open(blockEditorModel); + } + + vm.showCreateDialog = showCreateDialog; + function showCreateDialog(createIndex, $event) { + + if (vm.blockTypePicker) { + return; + } + + if (vm.availableBlockTypes.length === 0) { + return; + } + + var amountOfAvailableTypes = vm.availableBlockTypes.length; + var blockPickerModel = { + $parentScope: $scope, // pass in a $parentScope, this maintains the scope inheritance in infinite editing + $parentForm: vm.propertyForm, // pass in a $parentForm, this maintains the FormController hierarchy with the infinite editing view (if it contains a form) + availableItems: vm.availableBlockTypes, + title: vm.labels.grid_addElement, + orderBy: "$index", + view: "views/common/infiniteeditors/blockpicker/blockpicker.html", + size: (amountOfAvailableTypes > 8 ? "medium" : "small"), + filter: (amountOfAvailableTypes > 8), + clickPasteItem: function(item, mouseEvent) { + if (item.type === "elementTypeArray") { + var indexIncrementor = 0; + item.pasteData.forEach(function (entry) { + if (requestPasteFromClipboard(createIndex + indexIncrementor, entry)) { + indexIncrementor++; + } + }); + } else { + requestPasteFromClipboard(createIndex, item.pasteData); + } + if(!(mouseEvent.ctrlKey || mouseEvent.metaKey)) { + blockPickerModel.close(); + } + }, + submit: function(blockPickerModel, mouseEvent) { + var added = false; + if (blockPickerModel && blockPickerModel.selectedItem) { + added = addNewBlock(createIndex, blockPickerModel.selectedItem.blockConfigModel.contentTypeKey); + } + + if(!(mouseEvent.ctrlKey || mouseEvent.metaKey)) { + editorService.close(); + if (added && vm.layout.length > createIndex) { + if (inlineEditing === true) { + activateBlock(vm.layout[createIndex].$block); + } else if (inlineEditing === false && vm.layout[createIndex].$block.hideContentInOverlay !== true) { + editBlock(vm.layout[createIndex].$block, false, createIndex, blockPickerModel.$parentForm); + } + } + } + }, + close: function() { + // if opned by a inline creator button(index less than length), we want to move the focus away, to hide line-creator. + if (createIndex < vm.layout.length) { + vm.setBlockFocus(vm.layout[Math.max(createIndex-1, 0)].$block); + } + + editorService.close(); + } + }; + + blockPickerModel.clickClearClipboard = function ($event) { + clipboardService.clearEntriesOfType("elementType", vm.availableContentTypesAliases); + clipboardService.clearEntriesOfType("elementTypeArray", vm.availableContentTypesAliases); + }; + + blockPickerModel.clipboardItems = []; + + var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", vm.availableContentTypesAliases); + singleEntriesForPaste.forEach(function (entry) { + blockPickerModel.clipboardItems.push( + { + type: "elementType", + pasteData: entry.data, + blockConfigModel: modelObject.getScaffoldFromAlias(entry.alias), + elementTypeModel: { + name: entry.label, + icon: entry.icon + } + } + ); + }); + + var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", vm.availableContentTypesAliases); + arrayEntriesForPaste.forEach(function (entry) { + blockPickerModel.clipboardItems.push( + { + type: "elementTypeArray", + pasteData: entry.data, + blockConfigModel: {}, // no block configuration for paste items of elementTypeArray. + elementTypeModel: { + name: entry.label, + icon: entry.icon + } + } + ); + }); + + // open block picker overlay + editorService.open(blockPickerModel); + + }; + + var requestCopyAllBlocks = function() { + + var elementTypesToCopy = vm.layout.filter(entry => entry.$block.config.unsupported !== true).map(entry => entry.$block.content); + + // list aliases + var aliases = elementTypesToCopy.map(content => content.contentTypeAlias); + + // remove dublicates + aliases = aliases.filter((item, index) => aliases.indexOf(item) === index); + + var contentNodeName = ""; + if(vm.umbVariantContent) { + contentNodeName = vm.umbVariantContent.editor.content.name; + } else if (vm.umbElementEditorContent) { + contentNodeName = vm.umbElementEditorContent.model.documentType.name + } + + localizationService.localize("clipboard_labelForArrayOfItemsFrom", [vm.model.label, contentNodeName]).then(function(localizedLabel) { + clipboardService.copyArray("elementTypeArray", aliases, elementTypesToCopy, localizedLabel, "icon-thumbnail-list", vm.model.id); + }); + } + function copyBlock(block) { + clipboardService.copy("elementType", block.content.contentTypeAlias, block.content, block.label); + } + function requestPasteFromClipboard(index, pasteEntry) { + + if (pasteEntry === undefined) { + return false; + } + + var layoutEntry = modelObject.createFromElementType(pasteEntry); + if (layoutEntry === null) { + return false; + } + + // make block model + var blockObject = getBlockObject(layoutEntry); + if (blockObject === null) { + return false; + } + + // set the BlockObject on our layout entry. + layoutEntry.$block = blockObject; + + // insert layout entry at the decired location in layout. + vm.layout.splice(index, 0, layoutEntry); + + vm.currentBlockInFocus = blockObject; + + return true; + + } + function requestDeleteBlock(block) { + localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockMessage", "contentTypeEditor_yesDelete"]).then(function (data) { + const overlay = { + title: data[0], + content: localizationService.tokenReplace(data[1], [block.label]), + submitButtonLabel: data[2], + close: function () { + overlayService.close(); + }, + submit: function () { + deleteBlock(block); + overlayService.close(); + } + }; + + overlayService.confirmDelete(overlay); + }); + } + function requestDeleteAllBlocks() { + localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(function (data) { + overlayService.confirmDelete({ + title: data[1], + content: data[0], + close: function () { + overlayService.close(); + }, + submit: function () { + deleteAllBlocks(); + overlayService.close(); + } + }); + }); + } + + function openSettingsForBlock(block, blockIndex, parentForm) { + editBlock(block, true, blockIndex, parentForm); + } + + vm.blockEditorApi = { + activateBlock: activateBlock, + editBlock: editBlock, + copyBlock: copyBlock, + requestDeleteBlock: requestDeleteBlock, + deleteBlock: deleteBlock, + openSettingsForBlock: openSettingsForBlock + } + + vm.sortableOptions = { + axis: "y", + cursor: "grabbing", + handle: ".blockelement__draggable-element", + cancel: "input,textarea,select,option", + classes: ".blockelement--dragging", + distance: 5, + tolerance: "pointer", + scroll: true, + update: function (ev, ui) { + setDirty(); + } + }; + + + function onAmountOfBlocksChanged() { + + // enable/disable property actions + copyAllBlocksAction.isDisabled = vm.layout.length === 0; + deleteAllBlocksAction.isDisabled = vm.layout.length === 0; + + // validate limits: + if (vm.propertyForm) { + + var isMinRequirementGood = vm.validationLimit.min === null || vm.layout.length >= vm.validationLimit.min; + vm.propertyForm.minCount.$setValidity("minCount", isMinRequirementGood); + + var isMaxRequirementGood = vm.validationLimit.max === null || vm.layout.length <= vm.validationLimit.max; + vm.propertyForm.maxCount.$setValidity("maxCount", isMaxRequirementGood); + + } + } + unsubscribe.push($scope.$watch(() => vm.layout.length, onAmountOfBlocksChanged)); + + + $scope.$on("$destroy", function () { + for (const subscription of unsubscribe) { + subscription(); + } + }); + + + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.createButton.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.createButton.controller.js new file mode 100644 index 0000000000..98d4f4ea3a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.createButton.controller.js @@ -0,0 +1,18 @@ +(function () { + "use strict"; + + angular + .module("umbraco") + .controller("Umbraco.PropertyEditors.BlockListPropertyEditor.CreateButtonController", + function Controller($scope) { + + var vm = this; + vm.plusPosX = 0; + + vm.onMouseMove = function($event) { + vm.plusPosX = $event.offsetX; + } + + }); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbblocklistblock.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbblocklistblock.component.js new file mode 100644 index 0000000000..4474dbd55c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbblocklistblock.component.js @@ -0,0 +1,76 @@ +(function () { + "use strict"; + + /** + * @ngdoc directive + * @name umbraco.directives.directive:umbBlockListBlock + * @description + * The component to render the view for a block. + * If a stylesheet is used then this uses a ShadowDom to make a scoped element. + * This way the backoffice styling does not collide with the block style. + */ + + angular + .module("umbraco") + .component("umbBlockListBlock", { + controller: BlockListBlockController, + controllerAs: "model", + bindings: { + stylesheet: "@", + view: "@", + block: "=", + api: "<", + index: "<", + parentForm: "<" + }, + require: { + valFormManager: "^^valFormManager" + } + } + ); + + function BlockListBlockController($scope, $compile, $element) { + var model = this; + + model.$onInit = function () { + // This is ugly and is only necessary because we are not using components and instead + // relying on ng-include. It is definitely possible to compile the contents + // of the view into the DOM using $templateCache and $http instead of using + // ng - include which means that the controllerAs flows directly to the view. + // This would mean that any custom components would need to be updated instead of relying on $scope. + // Guess we'll leave it for now but means all things need to be copied to the $scope and then all + // primitives need to be watched. + + $scope.block = model.block; + $scope.api = model.api; + $scope.index = model.index; + $scope.parentForm = model.parentForm; + $scope.valFormManager = model.valFormManager; + + if (model.stylesheet) { + // TODO: Not sure why this needs a prefixed /? this means it will never work in a virtual directory + model.stylesheet = "/" + model.stylesheet; + var shadowRoot = $element[0].attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = ` + +
    + `; + $compile(shadowRoot)($scope); + } + else { + $element.append($compile('
    ')($scope)); + } + }; + + // We need to watch for changes on primitive types and upate the $scope values. + model.$onChanges = function (changes) { + if (changes.index) { + $scope.index = changes.index.currentValue; + } + } + } + + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbblocklistrow.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbblocklistrow.component.js new file mode 100644 index 0000000000..a79ee557a2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbblocklistrow.component.js @@ -0,0 +1,37 @@ +(function () { + "use strict"; + + /** + * @ngdoc directive + * @name umbraco.directives.directive:umbBlockListRow + * @description + * renders each row for the block list editor + */ + + angular + .module("umbraco") + .component("umbBlockListRow", { + templateUrl: 'views/propertyeditors/blocklist/umb-block-list-row.html', + controller: BlockListRowController, + controllerAs: "vm", + bindings: { + blockEditorApi: "<", + layout: "<", + index: "<" + }, + require: { + valFormManager: "^^valFormManager" + } + } + ); + + function BlockListRowController($scope) { + + var vm = this; + + vm.$onInit = function () { + + }; + } + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index c82a3c6204..f87c88a467 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -3,7 +3,7 @@

    -
    +
    • 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 31c5232278..11a3b58c55 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 @@ -65,7 +65,7 @@ } }); - function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) { + function NestedContentController($scope, $interpolate, $filter, serverValidationManager, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) { var vm = this; var model = $scope.$parent.$parent.model; @@ -297,8 +297,15 @@ } function deleteNode(idx) { - vm.nodes.splice(idx, 1); + var removed = vm.nodes.splice(idx, 1); + setDirty(); + + removed.forEach(x => { + // remove any server validation errors associated + serverValidationManager.removePropertyError(x.key, vm.umbProperty.property.culture, vm.umbProperty.property.segment, "", { matchType: "contains" }); + }); + updateModel(); validate(); }; @@ -479,7 +486,8 @@ var notSupported = [ "Umbraco.Tags", "Umbraco.UploadField", - "Umbraco.ImageCropper" + "Umbraco.ImageCropper", + "Umbraco.BlockList" ]; // Initialize @@ -585,8 +593,14 @@ for (var p = 0; p < tab.properties.length; p++) { var prop = tab.properties[p]; + // store the original alias before we change below, see notes prop.propertyAlias = prop.alias; + + // NOTE: This is super ugly, the reason it is like this is because it controls the label/html id in the umb-property component at a higher level. + // not pretty :/ but we can't change this now since it would require a bunch of plumbing to be able to change the id's higher up. prop.alias = model.alias + "___" + prop.alias; + + // TODO: Do we need to deal with this separately? // Force validation to occur server side as this is the // only way we can have consistency between mandatory and // regex validation messages. Not ideal, but it works. @@ -661,8 +675,8 @@ ]; this.$onInit = function () { - if (this.umbProperty) { - this.umbProperty.setPropertyActions(propertyActions); + if (vm.umbProperty) { + vm.umbProperty.setPropertyActions(propertyActions); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html index a2105c5675..125e920fe6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.editor.html @@ -1,13 +1,19 @@ 
      -
      +
      - - - + -
      + -

      {{property.notSupportedMessage}}

      +
      -
      +
      + +

      {{property.notSupportedMessage}}

      + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html index 6133109d23..2f3856b101 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html @@ -6,30 +6,41 @@
      -
      +
      -
      + -
      +
      -
      - - +
      + +
      + +
      + + +
      +
      + + +
      + +
      -
      - -
      - -
      +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/notsupported/notsupported.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/notsupported/notsupported.html new file mode 100644 index 0000000000..a2fbb0e907 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/notsupported/notsupported.html @@ -0,0 +1,3 @@ +
      + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/notsupported/notsupported.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/notsupported/notsupported.less new file mode 100644 index 0000000000..5eaec3f67b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/notsupported/notsupported.less @@ -0,0 +1,7 @@ +.umb-property-editor.umb-property-editor--notsupported { + background-color: @red; + color: white; + padding: 5px 10px; + width: auto; + border-radius: @baseBorderRadius * 2; +} diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index 4e3a78144b..d0f59c110f 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -10,6 +10,8 @@ module.exports = function (config) { // list of files / patterns to load in the browser files: [ + // Jasmine plugins + //libraries 'node_modules/jquery/dist/jquery.min.js', 'node_modules/angular/angular.js', @@ -98,7 +100,14 @@ module.exports = function (config) { // - PhantomJS // - IE (only Windows) // CLI --browsers Chrome,Firefox,Safari - browsers: ['PhantomJS'], + browsers: ['ChromeHeadless'], + + customLaunchers: { + ChromeDebugging: { + base: 'Chrome', + flags: ['--remote-debugging-port=9333'] + } + }, // allow waiting a bit longer, some machines require this @@ -115,6 +124,7 @@ module.exports = function (config) { plugins: [ require('karma-jasmine'), require('karma-phantomjs-launcher'), + require('karma-chrome-launcher'), require('karma-junit-reporter'), require('karma-spec-reporter') diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/filters/truncate-filters.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/filters/truncate-filters.spec.js index 1e9ea2ea46..64a984b499 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/filters/truncate-filters.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/filters/truncate-filters.spec.js @@ -53,7 +53,6 @@ testCases.forEach(function(test){ it('Expects \'' + test.input + '\' to be truncated as \''+ test.expectedResult + '\', when noOfChars=' + test.noOfChars + ', and appendDots=' + test.appendDots, function() { - console.log($truncate(test.input, test.noOfChars, test.appendDots)); expect($truncate(test.input, test.noOfChars, test.appendDots)).toBe(test.expectedResult); }); }); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/block-editor-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/block-editor-service.spec.js new file mode 100644 index 0000000000..71404b5e85 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/block-editor-service.spec.js @@ -0,0 +1,422 @@ +describe('blockEditorService tests', function () { + + var contentKey = "6A1F5BDD-67EF-4173-B061-D6348ED07094"; + var contentUdi = "umb://element/6A1F5BDD67EF4173B061D6348ED07094"; + var settingsKey = "2AF42343-C8A2-400D-BA43-4818C2B3CDC5"; + var settingsUdi = "umb://element/2AF42343C8A2400DBA434818C2B3CDC5"; + + var blockEditorService, contentResource, $rootScope, $scope, $q, localizationService, $timeout; + + beforeEach(module('umbraco.services')); + beforeEach(module('umbraco.resources')); + beforeEach(module('umbraco.mocks')); + beforeEach(module('umbraco')); + + beforeEach(inject(function ($injector, mocksUtils, _$rootScope_, _$q_, _$timeout_) { + + mocksUtils.disableAuth(); + + $rootScope = _$rootScope_; + $scope = $rootScope.$new(); + $q = _$q_; + $timeout = _$timeout_; + + contentResource = $injector.get("contentResource"); + spyOn(contentResource, "getScaffoldByKey").and.callFake( + function () { + var scaffold = mocksUtils.getMockVariantContent(1234, contentKey, contentUdi); + return $q.resolve(scaffold); + } + ); + // this seems to be required because of the poor promise implementation in localizationService (see TODO in that service) + localizationService = $injector.get("localizationService"); + spyOn(localizationService, "localize").and.callFake( + function () { + return $q.resolve("Localized test text"); + } + ); + + blockEditorService = $injector.get('blockEditorService'); + + })); + + + var blockConfigurationMock = { contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", label: "Test label", settingsElementTypeKey: null, view: "testview.html" }; + + var propertyModelMock = { + layout: { + "Umbraco.TestBlockEditor": [ + { + contentUdi: contentUdi + } + ] + }, + contentData: [ + { + udi: contentUdi, + contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", + testproperty: "myTestValue" + } + ] + }; + + var blockWithSettingsConfigurationMock = { contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", label: "Test label", settingsElementTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", view: "testview.html" }; + var propertyModelWithSettingsMock = { + layout: { + "Umbraco.TestBlockEditor": [ + { + contentUdi: contentUdi, + settingsUdi: settingsUdi + } + ] + }, + contentData: [ + { + udi: contentUdi, + contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", + testproperty: "myTestValue" + } + ], + settingsData: [ + { + udi: settingsUdi, + contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", + testproperty: "myTestValueForSettings" + } + ] + }; + + describe('init blockEditorModelObject', function () { + + it('fail if no model value', function () { + function createWithNoModelValue() { + blockEditorService.createModelObject(null, "Umbraco.TestBlockEditor", [], $scope, $scope); + } + expect(createWithNoModelValue).toThrow(); + }); + + it('return a object, with methods', function () { + var modelObject = blockEditorService.createModelObject({}, "Umbraco.TestBlockEditor", [], $scope, $scope); + + expect(modelObject).not.toBeUndefined(); + expect(modelObject.load).not.toBeUndefined(); + }); + + it('getBlockConfiguration provide the requested block configurtion', function () { + var modelObject = blockEditorService.createModelObject({}, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + expect(modelObject.getBlockConfiguration(blockConfigurationMock.contentTypeKey).label).toBe(blockConfigurationMock.label); + }); + + it('load provides data for itemPicker', function (done) { + var modelObject = blockEditorService.createModelObject({}, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + try { + var itemPickerOptions = modelObject.getAvailableBlocksForBlockPicker(); + expect(itemPickerOptions.length).toBe(1); + expect(itemPickerOptions[0].blockConfigModel.contentTypeKey).toBe(blockConfigurationMock.contentTypeKey); + done(); + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + it('getLayoutEntry has values', function (done) { + + + var modelObject = blockEditorService.createModelObject(propertyModelMock, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + try { + var layout = modelObject.getLayout(); + + expect(layout).not.toBeUndefined(); + expect(layout.length).toBe(1); + expect(layout[0]).toBe(propertyModelMock.layout["Umbraco.TestBlockEditor"][0]); + expect(layout[0].udi).toBe(propertyModelMock.layout["Umbraco.TestBlockEditor"][0].udi); + + done(); + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + it('getBlockObject has values', function (done) { + + + var modelObject = blockEditorService.createModelObject(propertyModelMock, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + try { + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + expect(blockObject).not.toBeUndefined(); + expect(blockObject.data.udi).toBe(propertyModelMock.contentData[0].udi); + expect(blockObject.content.variants[0].tabs[0].properties[0].value).toBe(propertyModelMock.contentData[0].testproperty); + + done(); + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + + it('getBlockObject syncs primitive values', function (done) { + + var propertyModel = angular.copy(propertyModelMock); + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + try { + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + blockObject.content.variants[0].tabs[0].properties[0].value = "anotherTestValue"; + + // invoke angularJS Store. + $timeout(function () { + expect(blockObject.data).toEqual(propertyModel.contentData[0]); + expect(blockObject.data.testproperty).toBe("anotherTestValue"); + expect(propertyModel.contentData[0].testproperty).toBe("anotherTestValue"); + + done(); + }); + + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + + }); + + + it('getBlockObject syncs values of object', function (done) { + + var propertyModel = angular.copy(propertyModelMock); + + var complexValue = { "list": ["A", "B", "C"] }; + propertyModel.contentData[0].testproperty = complexValue; + + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + try { + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + blockObject.content.variants[0].tabs[0].properties[0].value.list[0] = "AA"; + blockObject.content.variants[0].tabs[0].properties[0].value.list.push("D"); + + // invoke angularJS Store. + $timeout(function () { + expect(propertyModel.contentData[0].testproperty.list[0]).toBe("AA"); + expect(propertyModel.contentData[0].testproperty.list.length).toBe(4); + + done(); + }); + + + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + it('layout is referencing layout of propertyModel', function (done) { + + var propertyModel = angular.copy(propertyModelMock); + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + var layout = modelObject.getLayout(); + + // remove from layout; + layout.splice(0, 1); + + expect(propertyModel.layout["Umbraco.TestBlockEditor"].length).toBe(0); + expect(propertyModel.layout["Umbraco.TestBlockEditor"][0]).toBeUndefined(); + + done(); + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + it('removeDataAndDestroyModel removes data', function (done) { + + var propertyModel = angular.copy(propertyModelMock); + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + try { + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + expect(blockObject).not.toBeUndefined(); + expect(blockObject).not.toBe(null); + + // remove from layout; + layout.splice(0, 1); + + // remove from data; + modelObject.removeDataAndDestroyModel(blockObject); + + expect(propertyModel.contentData.length).toBe(0); + expect(propertyModel.contentData[0]).toBeUndefined(); + expect(propertyModel.layout["Umbraco.TestBlockEditor"].length).toBe(0); + expect(propertyModel.layout["Umbraco.TestBlockEditor"][0]).toBeUndefined(); + + done(); + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + it('getBlockObject of block with settings has values', function (done) { + + var propertyModel = angular.copy(propertyModelWithSettingsMock); + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockWithSettingsConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + expect(blockObject).not.toBeUndefined(); + expect(blockObject.data.udi).toBe(propertyModel.contentData[0].udi); + expect(blockObject.content.variants[0].tabs[0].properties[0].value).toBe(propertyModel.contentData[0].testproperty); + + done(); + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + + it('getBlockObject of block with settings syncs primative values', function (done) { + + var propertyModel = angular.copy(propertyModelWithSettingsMock); + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockWithSettingsConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + blockObject.content.variants[0].tabs[0].properties[0].value = "anotherTestValue"; + blockObject.settings.variants[0].tabs[0].properties[0].value = "anotherTestValueForSettings"; + + // invoke angularJS Store. + $timeout(function () { + expect(blockObject.data).toEqual(propertyModel.contentData[0]); + expect(blockObject.data.testproperty).toBe("anotherTestValue"); + expect(propertyModel.contentData[0].testproperty).toBe("anotherTestValue"); + + expect(blockObject.settingsData).toEqual(propertyModel.settingsData[0]); + expect(blockObject.settingsData.testproperty).toBe("anotherTestValueForSettings"); + expect(propertyModel.settingsData[0].testproperty).toBe("anotherTestValueForSettings"); + + done(); + }); + + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + + it('getBlockObject of block with settings syncs values of object', function (done) { + + var propertyModel = angular.copy(propertyModelWithSettingsMock); + + var complexValue = { "list": ["A", "B", "C"] }; + propertyModel.contentData[0].testproperty = complexValue; + + var complexSettingsValue = { "list": ["A", "B", "C"] }; + propertyModel.settingsData[0].testproperty = complexSettingsValue; + + var modelObject = blockEditorService.createModelObject(propertyModel, "Umbraco.TestBlockEditor", [blockWithSettingsConfigurationMock], $scope, $scope); + + modelObject.load().then(() => { + + try { + var layout = modelObject.getLayout(); + + var blockObject = modelObject.getBlockObject(layout[0]); + + blockObject.content.variants[0].tabs[0].properties[0].value.list[0] = "AA"; + blockObject.content.variants[0].tabs[0].properties[0].value.list.push("D"); + + blockObject.settings.variants[0].tabs[0].properties[0].value.list[0] = "settingsValue"; + blockObject.settings.variants[0].tabs[0].properties[0].value.list.push("settingsNewValue"); + + // invoke angularJS Store. + $timeout(function () { + expect(propertyModel.contentData[0].testproperty.list[0]).toBe("AA"); + expect(propertyModel.contentData[0].testproperty.list.length).toBe(4); + + expect(propertyModel.settingsData[0].testproperty.list[0]).toBe("settingsValue"); + expect(propertyModel.settingsData[0].testproperty.list.length).toBe(4); + + done(); + }); + + + } catch (e) { + done.fail(e); + } + }); + + $rootScope.$digest(); + $timeout.flush(); + }); + + + }); + +}); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js index 5d2a618f46..ab2a5cdd69 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/server-validation-manager.spec.js @@ -1,10 +1,13 @@ describe('serverValidationManager tests', function () { - var serverValidationManager; + var $rootScope, serverValidationManager, $timeout; beforeEach(module('umbraco.services')); beforeEach(inject(function ($injector) { - serverValidationManager = $injector.get('serverValidationManager'); + $rootScope = $injector.get('$rootScope'); + $timeout = $injector.get('$timeout'); + serverValidationManager = $injector.get('serverValidationManager'); + serverValidationManager.clear(); })); describe('managing field validation errors', function () { @@ -314,6 +317,158 @@ }); + describe('managing complex editor validation errors', function () { + + // this root element doesn't have it's own attached errors, instead it has model state just + // showing that it has errors within it's nested properties. that ModelState is automatically + // added on the server side. + var nonRootLevelComplexValidationMsg = `[ + { + "$elementTypeAlias": "addressBook", + "$id": "34E3A26C-103D-4A05-AB9D-7E14032309C3", + "addresses": + [ + { + "$elementTypeAlias": "addressInfo", + "$id": "FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1", + "ModelState": + { + "_Properties.city.invariant.null.country": [ + "City is not in Australia" + ], + "_Properties.city.invariant.null.capital": [ + "Not a capital city" + ] + } + }, + { + "$elementTypeAlias": "addressInfo", + "$id": "7170A4DD-2441-4B1B-A8D3-437D75C4CBC9", + "ModelState": + { + "_Properties.city.invariant.null.country": [ + "City is not in Australia" + ], + "_Properties.city.invariant.null.capital": [ + "Not a capital city" + ] + } + } + ], + "ModelState": + { + "_Properties.addresses.invariant.null": [ + "" + ] + } + } +]`; + + it('create dictionary of id to ModelState', function () { + + //arrange + var complexValidationMsg = `[ + { + "$elementTypeAlias": "addressBook", + "$id": "34E3A26C-103D-4A05-AB9D-7E14032309C3", + "addresses": + [ + { + "$elementTypeAlias": "addressInfo", + "$id": "FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1", + "ModelState": + { + "_Properties.city.invariant.null.country": [ + "City is not in Australia" + ], + "_Properties.city.invariant.null.capital": [ + "Not a capital city" + ] + } + }, + { + "$elementTypeAlias": "addressInfo", + "$id": "7170A4DD-2441-4B1B-A8D3-437D75C4CBC9", + "ModelState": + { + "_Properties.city.invariant.null.country": [ + "City is not in Australia" + ], + "_Properties.city.invariant.null.capital": [ + "Not a capital city" + ] + } + } + ], + "ModelState": + { + "_Properties.addresses.invariant.null.counter": [ + "Must have at least 3 addresses" + ], + "_Properties.bookName.invariant.null.book": [ + "Invalid address book name" + ] + } + } +]`; + + //act + var ms = serverValidationManager.parseComplexEditorError(complexValidationMsg, "myBlockEditor"); + + //assert + expect(ms.length).toEqual(3); + expect(ms[0].validationPath).toEqual("myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3"); + expect(ms[1].validationPath).toEqual("myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1"); + expect(ms[2].validationPath).toEqual("myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/7170A4DD-2441-4B1B-A8D3-437D75C4CBC9"); + + }); + + it('create dictionary of id to ModelState with inherited errors', function () { + + //act + var ms = serverValidationManager.parseComplexEditorError(nonRootLevelComplexValidationMsg, "myBlockEditor"); + + //assert + expect(ms.length).toEqual(3); + expect(ms[0].validationPath).toEqual("myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3"); + var item0ModelState = ms[0].modelState; + expect(Object.keys(item0ModelState).length).toEqual(1); + expect(item0ModelState["_Properties.addresses.invariant.null"].length).toEqual(1); + expect(item0ModelState["_Properties.addresses.invariant.null"][0]).toEqual(""); + expect(ms[1].validationPath).toEqual("myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1"); + expect(ms[2].validationPath).toEqual("myBlockEditor/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/7170A4DD-2441-4B1B-A8D3-437D75C4CBC9"); + + }); + + it('add errors for ModelState with inherited errors', function () { + + //act + let modelState = { + "_Properties.blockFeatures.invariant.null": [ + nonRootLevelComplexValidationMsg + ] + }; + serverValidationManager.addErrorsForModelState(modelState); + + //assert + var propertyErrors = [ + "blockFeatures", + "blockFeatures/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses", + "blockFeatures/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", + "blockFeatures/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/7170A4DD-2441-4B1B-A8D3-437D75C4CBC9/city" + ] + // These will all exist + propertyErrors.forEach(x => expect(serverValidationManager.getPropertyError(x)).toBeDefined()); + + // These field errors also exist + expect(serverValidationManager.getPropertyError(propertyErrors[2], null, "country")).toBeDefined(); + expect(serverValidationManager.getPropertyError(propertyErrors[2], null, "capital")).toBeDefined(); + expect(serverValidationManager.getPropertyError(propertyErrors[3], null, "country")).toBeDefined(); + expect(serverValidationManager.getPropertyError(propertyErrors[3], null, "capital")).toBeDefined(); + }); + + }); + describe('validation error subscriptions', function() { it('can subscribe to a field error', function() { @@ -327,7 +482,9 @@ allErrors: allErrors }; }, null); + + //act serverValidationManager.addFieldError("Name", "Required"); serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); @@ -348,7 +505,9 @@ var cb2 = function () { }; serverValidationManager.subscribe(null, null, "Name", cb1, null); + serverValidationManager.subscribe(null, null, "Title", cb2, null); + //act serverValidationManager.addFieldError("Name", "Required"); @@ -374,6 +533,7 @@ var args1; var args2; var numCalled = 0; + var numCalledWithErrors = 0; //arrange serverValidationManager.subscribe("myProperty", null, "value1", function (isValid, propertyErrors, allErrors) { @@ -384,15 +544,19 @@ }; }, null); + serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) { numCalled++; + if (propertyErrors.length > 0) { + numCalledWithErrors++; + } args2 = { isValid: isValid, propertyErrors: propertyErrors, allErrors: allErrors }; }, null); - + //act serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); serverValidationManager.addPropertyError("myProperty", null, "value2", "Some value 2", null); @@ -415,28 +579,36 @@ expect(args2.propertyErrors[0].errorMsg).toEqual("Some value 1"); expect(args2.propertyErrors[1].errorMsg).toEqual("Some value 2"); expect(args2.allErrors.length).toEqual(2); - //Even though only 2 errors are added, the callback is called 3 times because any call to addPropertyError will invoke the callback - // if the property has errors existing. - expect(numCalled).toEqual(3); + //3 errors are added but a call to subscribe also calls the callback + expect(numCalled).toEqual(4); + expect(numCalledWithErrors).toEqual(3); }); it('can subscribe to a culture error for both a property and its sub field', function () { var args1; var args2; var numCalled = 0; + var numCalledWithErrors = 0; //arrange serverValidationManager.subscribe(null, "en-US", null, function (isValid, propertyErrors, allErrors) { numCalled++; + if (propertyErrors.length > 0) { + numCalledWithErrors++; + } args1 = { isValid: isValid, propertyErrors: propertyErrors, allErrors: allErrors }; }, null); + serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) { numCalled++; + if (propertyErrors.length > 0) { + numCalledWithErrors++; + } args2 = { isValid: isValid, propertyErrors: propertyErrors, @@ -445,20 +617,162 @@ }, null); //act - serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); - serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null); - serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2", null); - serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null); + serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null); // doesn't match + serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1", null); // matches callback 1 + serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2", null); // matches callback 1 + serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null); // doesn't match - but all callbacks still execute //assert - expect(args1).not.toBeUndefined(); expect(args1.isValid).toBe(false); + expect(args2.isValid).toBe(true); // no errors registered for this callback - expect(args2).toBeUndefined(); - - expect(numCalled).toEqual(2); + expect(numCalled).toEqual(10); // both subscriptions will be called once per addPropertyError and also called on subscribe + expect(numCalledWithErrors).toEqual(3); // the first subscription is called 3 times with errors because the 4th time we call addPropertyError all callbacks still execute }); - + + it('can subscribe to a property validation path prefix', function () { + var callbackA = []; + var callbackB = []; + + //arrange + serverValidationManager.subscribe("myProperty", null, null, function (isValid, propertyErrors, allErrors) { + if (propertyErrors.length > 0) { + callbackA.push(propertyErrors); + } + }, null, { matchType: "prefix" }); + + serverValidationManager.subscribe("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses", null, null, function (isValid, propertyErrors, allErrors) { + if (propertyErrors.length > 0) { + callbackB.push(propertyErrors); + } + }, null, { matchType: "prefix" }); + + //act + // will match A: + serverValidationManager.addPropertyError("myProperty", null, null, "property error", null); + serverValidationManager.addPropertyError("myProperty", null, "value1", "value error", null); + // will match A + B + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, null, "property error", null); + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, "value1", "value error", null); + // won't match: + serverValidationManager.addPropertyError("myProperty", "en-US", null, "property error", null); + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", "en-US", null, "property error", null); + serverValidationManager.addPropertyError("otherProperty", null, null, "property error", null); + serverValidationManager.addPropertyError("otherProperty", null, "value1", "value error", null); + + //assert + + // both will be called each time addPropertyError is called + expect(callbackA.length).toEqual(8); + expect(callbackB.length).toEqual(6); // B - will only be called 6 times with errors because the first 2 calls to addPropertyError haven't added errors for B yet + expect(callbackA[callbackA.length - 1].length).toEqual(4); // 4 errors for A + expect(callbackB[callbackB.length - 1].length).toEqual(2); // 2 errors for B + + // clear the data and notify + callbackA = []; + callbackB = []; + + serverValidationManager.notify(); + $timeout.flush(); + + expect(callbackA.length).toEqual(1); + expect(callbackB.length).toEqual(1); + expect(callbackA[0].length).toEqual(4); // 4 errors for A + expect(callbackB[0].length).toEqual(2); // 2 errors for B + + }); + + it('can subscribe to a property validation path suffix', function () { + var callbackA = []; + var callbackB = []; + + //arrange + serverValidationManager.subscribe("myProperty", null, null, function (isValid, propertyErrors, allErrors) { + if (propertyErrors.length > 0) { + callbackA.push(propertyErrors); + } + }, null, { matchType: "suffix" }); + + serverValidationManager.subscribe("city", null, null, function (isValid, propertyErrors, allErrors) { + if (propertyErrors.length > 0) { + callbackB.push(propertyErrors); + } + }, null, { matchType: "suffix" }); + + //act + // will match A: + serverValidationManager.addPropertyError("myProperty", null, null, "property error", null); + serverValidationManager.addPropertyError("myProperty", null, "value1", "value error", null); + // will match B + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, null, "property error", null); + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, "value1", "value error", null); + // won't match: + serverValidationManager.addPropertyError("myProperty", "en-US", null, "property error", null); + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", "en-US", null, "property error", null); + serverValidationManager.addPropertyError("otherProperty", null, null, "property error", null); + serverValidationManager.addPropertyError("otherProperty", null, "value1", "value error", null); + + //assert + + // both will be called each time addPropertyError is called + expect(callbackA.length).toEqual(8); + expect(callbackB.length).toEqual(6); // B - will only be called 6 times with errors because the first 2 calls to addPropertyError haven't added errors for B yet + expect(callbackA[callbackA.length - 1].length).toEqual(2); // 2 errors for A + expect(callbackB[callbackB.length - 1].length).toEqual(2); // 2 errors for B + + // clear the data and notify + callbackA = []; + callbackB = []; + + serverValidationManager.notify(); + $timeout.flush(); + + expect(callbackA.length).toEqual(1); + expect(callbackB.length).toEqual(1); + expect(callbackA[0].length).toEqual(2); // 2 errors for A + expect(callbackB[0].length).toEqual(2); // 2 errors for B + + }); + + it('can subscribe to a property validation path contains', function () { + var callbackA = []; + + //arrange + serverValidationManager.subscribe("addresses", null, null, function (isValid, propertyErrors, allErrors) { + if (propertyErrors.length > 0) { + callbackA.push(propertyErrors); + } + }, null, { matchType: "contains" }); + + //act + // will match A: + serverValidationManager.addPropertyError("addresses", null, null, "property error", null); + serverValidationManager.addPropertyError("addresses", null, "value1", "value error", null); + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, null, "property error", null); + serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, "value1", "value error", null); + // won't match: + serverValidationManager.addPropertyError("addresses", "en-US", null, "property error", null); + serverValidationManager.addPropertyError("addresses/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", "en-US", null, "property error", null); + serverValidationManager.addPropertyError("otherProperty", null, null, "property error", null); + serverValidationManager.addPropertyError("otherProperty", null, "value1", "value error", null); + + //assert + + // both will be called each time addPropertyError is called + expect(callbackA.length).toEqual(8); + expect(callbackA[callbackA.length - 1].length).toEqual(4); // 4 errors for A + + // clear the data and notify + callbackA = []; + + serverValidationManager.notify(); + $timeout.flush(); + + expect(callbackA.length).toEqual(1); + expect(callbackA[0].length).toEqual(4); // 4 errors for A + + }); + // TODO: Finish testing the rest! }); diff --git a/src/Umbraco.Web.UI.Docs/gulpfile.js b/src/Umbraco.Web.UI.Docs/gulpfile.js index a3e596ecad..a7269ec8b7 100644 --- a/src/Umbraco.Web.UI.Docs/gulpfile.js +++ b/src/Umbraco.Web.UI.Docs/gulpfile.js @@ -8,6 +8,8 @@ var connect = require('gulp-connect'); var open = require('gulp-open'); var gulpDocs = require('gulp-ngdocs'); +var documentationFiles = ['../Umbraco.Web.UI.Client/src/common/**/*.js', './src/api/**/*.ngdoc']; + /************************** * Build Backoffice UI API documentation **************************/ @@ -24,14 +26,15 @@ gulp.task('docs', [], function (cb) { return gulpDocs.sections({ api: { - glob: ['../Umbraco.Web.UI.Client/src/common/**/*.js', './src/api/**/*.ngdoc'], + glob: documentationFiles, api: true, title: 'UI API Documentation' } }) .pipe(gulpDocs.process(options)) - .pipe(gulp.dest('./api')); - cb(); + .pipe(gulp.dest('./api')) + .pipe(connect.reload()); + }); gulp.task('connect:docs', function (cb) { @@ -44,6 +47,10 @@ gulp.task('connect:docs', function (cb) { cb(); }); +gulp.task('watch:docs', function (cb) { + return gulp.watch(documentationFiles, ['docs']); +}); + gulp.task('open:docs', function (cb) { var options = { @@ -54,3 +61,6 @@ gulp.task('open:docs', function (cb) { .pipe(open(options)); cb(); }); + +gulp.task('watch', ['docs', 'connect:docs', 'open:docs', 'watch:docs']); + diff --git a/src/Umbraco.Web.UI.Docs/package.json b/src/Umbraco.Web.UI.Docs/package.json index 843740482e..f216b4b129 100644 --- a/src/Umbraco.Web.UI.Docs/package.json +++ b/src/Umbraco.Web.UI.Docs/package.json @@ -3,7 +3,10 @@ "scripts": { "docs": "gulp docs", "start": "gulp docs", - "default": "gulp docs" + "default": "gulp docs", + "dev": "gulp watch", + "serve": "gulp watch", + "watch": "gulp watch" }, "devDependencies": { "gulp": "^3.9.1", diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index fc4ad6402c..ea34857715 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -285,6 +285,7 @@ + Web.Template.config @@ -348,6 +349,7 @@ 8800 / http://localhost:8800 + http://localhost:8700 False False diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index aaebcfd1d8..8e4e5bbbfd 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -17,6 +17,7 @@ Opret gruppe Slet Deaktivér + Edit settings Tøm papirkurv Aktivér Eksportér dokumenttype @@ -152,6 +153,8 @@ Fortryd Bekræft Flere publiseringsmuligheder + Indsæt + Indsæt og luk For @@ -282,7 +285,7 @@ Tilføj en ny tekstboks Fjern denne tekstboks Indholdsrod - Medtag udkast: udgiv også ikke-publicerede sider. + Inkluder ikke-udgivet indhold. Denne værdi er skjult.Hvis du har brug for adgang til at se denne værdi, bedes du kontakte din web-administrator. Denne værdi er skjult. Hvilke sprog vil du gerne udgive? Alle sprog med indhold gemmes! @@ -608,6 +611,7 @@ Du skal stå til venstre for de 2 celler du ønsker at samle! Du kan ikke opdele en celle, som ikke allerede er delt. Denne egenskab er ugyldig + Feltet %0% bruger editor %1% som ikke er supporteret for ElementTyper. Om @@ -1813,8 +1817,43 @@ Mange hilsner fra Umbraco robotten Kopier %0% %0% fra %1% Fjern alle elementer + Ryd udklipsholder Åben egenskabshandlinger + + Opret ny blok + Tilføj en indstillings afsnit + Tilføj visning + Tilføj stylesheet + Vælg billede + Opret ny + Overskriv stylesheet + Tilføj stylesheet + Redigerings udseende + Data modeller + katalog udseende + Baggrunds farve + Ikon farve + Indholds model + Label + Speciel visning + Indstillings model + Rederings lagets størrelse + Tilføj speciel visning + Tilføj instillinger + Overskriv label form + %0%.]]> + Indhold der benytter sig af denne blok vil gå bort. + + Billede + Tilføj billede + Opret ny + Udklipsholder + Indstillinger + Avanceret + Skjuld indholds editoren + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 8d3ca48274..44cdcc41f0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1,2457 +1,2500 @@ - - - - The Umbraco community - https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files - - - Culture and Hostnames - Audit Trail - Browse Node - Change Document Type - Copy - Create - Export - Create Package - Create group - Delete - Disable - Empty recycle bin - Enable - Export Document Type - Import Document Type - Import Package - Edit in Canvas - Exit - Move - Notifications - Public access - Publish - Unpublish - Reload - Republish entire site - Rename - Restore - Set permissions for the page %0% - Choose where to copy - Choose where to move - to in the tree structure below - Choose where to copy the selected item(s) - Choose where to move the selected item(s) - was moved to - was copied to - was deleted - Permissions - Rollback - Send To Publish - Send To Translation - Set group - Sort - Translate - Update - Set permissions - Unlock - Create Content Template - Resend Invitation - - - Content - Administration - Structure - Other - - - Allow access to assign culture and hostnames - Allow access to view a node's history log - Allow access to view a node - Allow access to change document type for a node - Allow access to copy a node - Allow access to create nodes - Allow access to delete nodes - Allow access to move a node - Allow access to set and change public access for a node - Allow access to publish a node - Allow access to unpublish a node - Allow access to change permissions for a node - Allow access to roll back a node to a previous state - Allow access to send a node for approval before publishing - Allow access to send a node for translation - Allow access to change the sort order for nodes - Allow access to translate a node - Allow access to save a node - Allow access to create a Content Template - - - Content - Info - - - Permission denied. - Add new Domain - remove - Invalid node. - One or more domains have an invalid format. - Domain has already been assigned. - Language - Domain - New domain '%0%' has been created - Domain '%0%' is deleted - Domain '%0%' has already been assigned - Domain '%0%' has been updated - Edit Current Domains - - - Inherit - Culture - - or inherit culture from parent nodes. Will also apply
      - to the current node, unless a domain below applies too.]]> -
      - Domains - - - Clear selection - Select - Do something else - Bold - Cancel Paragraph Indent - Insert form field - Insert graphic headline - Edit Html - Indent Paragraph - Italic - Center - Justify Left - Justify Right - Insert Link - Insert local link (anchor) - Bullet List - Numeric List - Insert macro - Insert picture - Publish and close - Publish with descendants - Edit relations - Return to list - Save - Save and close - Save and publish - Save and schedule - Save and send for approval - Save list view - Schedule - Preview - Save and preview - Preview is disabled because there's no template assigned - Choose style - Show styles - Insert table - Save and generate models - Undo - Redo - Delete tag - Cancel - Confirm - More publishing options - - - Viewing for - Content deleted - Content unpublished - Content saved and Published - Content saved and published for languages: %0% - Content saved - Content saved for languages: %0% - Content moved - Content copied - Content rolled back - Content sent for publishing - Content sent for publishing for languages: %0% - Sort child items performed by user - %0% - Copy - Publish - Publish - Move - Save - Save - Delete - Unpublish - Rollback - Send To Publish - Send To Publish - Sort - Custom - History (all variants) - - - To change the document type for the selected content, first select from the list of valid types for this location. - Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. - The content has been re-published. - Current Property - Current type - The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. - Document Type Changed - Map Properties - Map to Property - New Template - New Type - none - Content - Select New Document Type - The document type of the selected content has been successfully changed to [new type] and the following properties mapped: - to - Could not complete property mapping as one or more properties have more than one mapping defined. - Only alternate types valid for the current location are displayed. - - - Failed to create a folder under parent with ID %0% - Failed to create a folder under parent with name %0% - The folder name cannot contain illegal characters. - Failed to delete item: %0% - - - Is Published - About this page - Alias - (how would you describe the picture over the phone) - Alternative Links - Click to edit this item - Created by - Original author - Updated by - Created - Date/time this document was created - Document Type - Editing - Remove at - This item has been changed after publication - This item is not published - Last published - There are no items to show - There are no items to show in the list. - No content has been added - No members have been added - Media Type - Link to media item(s) - Member Group - Role - Member Type - No changes have been made - No date chosen - Page title - This media item has no link - Properties - This document is published but is not visible because the parent '%0%' is unpublished - This culture is published but is not visible because it is unpublished on parent '%0%' - This document is published but is not in the cache - Could not get the url - This document is published but its url would collide with content %0% - This document is published but its url cannot be routed - Publish - Published - Published (pending changes) - Publication Status - %0% and all content items underneath and thereby making their content publicly available.]]> - - Publish at - Unpublish at - Clear Date - Set date - Sortorder is updated - To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting - Statistics - Title (optional) - Alternative text (optional) - Type - Unpublish - Unpublished - Last edited - Date/time this document was edited - Remove file(s) - Click here to remove the image from the media item - Click here to remove the file from the media item - Link to document - Member of group(s) - Not a member of group(s) - Child items - Target - This translates to the following time on the server: - What does this mean?]]> - Are you sure you want to delete this item? - Property %0% uses editor %1% which is not supported by Nested Content. - Are you sure you want to delete all items? - No content types are configured for this property. - Add element type - Select element type - Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. - Enter an angular expression to evaluate against each item for its name. Use - to display the item index - Add another text box - Remove this text box - Content root - 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! - What languages would you like to publish? - What languages would you like to save? - All languages with content are saved on creation! - What languages would you like to send for approval? - What languages would you like to schedule? - Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages. - Published Languages - Unpublished Languages - Unmodified Languages - These languages haven't been created - - 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. - Create new - Paste from clipboard - This item is in the Recycle Bin - - - Create a new Content Template from '%0%' - Blank - Select a Content Template - Content Template created - A Content Template was created from '%0%' - Another Content Template with the same name already exists - A Content Template is predefined content that an editor can select to use as the basis for creating new content - - - Click to upload - or click here to choose files - You can drag files here to upload - Cannot upload this file, it does not have an approved file type - Max file size is - Media root - Failed to move media - Failed to copy media - Failed to create a folder under parent id %0% - Failed to rename the folder with id %0% - Drag and drop your file(s) into the area - - - Create a new member - All Members - Member groups have no additional properties for editing. - - - Where do you want to create the new %0% - Create an item under - Select the document type you want to make a content template for - Enter a folder name - Choose a type and a title - Document Types within the Settings section, by editing the Allowed child node types under Permissions.]]> - Document Types within the Settings section.]]> - The selected page in the content tree doesn't allow for any pages to be created below it. - Edit permissions for this document type - Create a new document type - 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 - New folder - New data type - New JavaScript file - New empty partial view - New partial view macro - New partial view from snippet - New partial view macro from snippet - New partial view macro (without macro) - New style sheet file - New Rich Text Editor style sheet file - - - Browse your website - - Hide - If Umbraco isn't opening, you might need to allow popups from this site - has opened in a new window - Restart - Visit - Welcome - - - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes - Publishing will make the selected items visible on the site. - Unpublishing will remove the selected items and all their descendants from the site. - Unpublishing will remove this page and all its descendants from the site. - You have unsaved changes. Making changes to the Document Type will discard the changes. - - - Done - Deleted %0% item - Deleted %0% items - Deleted %0% out of %1% item - Deleted %0% out of %1% items - Published %0% item - Published %0% items - Published %0% out of %1% item - Published %0% out of %1% items - Unpublished %0% item - Unpublished %0% items - Unpublished %0% out of %1% item - Unpublished %0% out of %1% items - Moved %0% item - Moved %0% items - Moved %0% out of %1% item - Moved %0% out of %1% items - Copied %0% item - Copied %0% items - Copied %0% out of %1% item - Copied %0% out of %1% items - - - Link title - Link - Anchor / querystring - Name - Manage hostnames - Close this window - Are you sure you want to delete - Are you sure you want to disable - Are you sure? - Are you sure? - Cut - Edit Dictionary Item - Edit Language - Edit selected media - Insert local link - Insert character - Insert graphic headline - Insert picture - Insert link - Click to add a Macro - Insert table - This will delete the language - Changing the culture for a language may be an expensive operation and will result in the content cache and indexes being rebuilt - Last Edited - Link - Internal link: - When using local links, insert "#" in front of link - Open in new window? - Macro Settings - This macro does not contain any properties you can edit - Paste - Edit permissions for - Set permissions for - Set permissions for %0% for user group %1% - Select the users groups you want to set permissions for - The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place - The recycle bin is now empty - When items are deleted from the recycle bin, they will be gone forever - regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> - Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' - Remove Macro - Required Field - Site is reindexed - The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished - The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. - Number of columns - Number of rows - Click on the image to see full size - Pick item - View Cache Item - Relate to original - Include descendants - The friendliest community - Link to page - Opens the linked document in a new window or tab - Link to media - Select content start node - Select media - Select media type - Select icon - Select item - Select link - Select macro - Select content - Select content type - Select media start node - Select member - Select member group - Select member type - Select node - Select sections - Select user - Select users - No icons were found - There are no parameters for this macro - There are no macros available to insert - External login providers - Exception Details - Stacktrace - Inner Exception - Link your - Un-link your - account - Select editor - 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. - - - There are no dictionary items. - - - %0%' below - ]]> - Culture Name - - Dictionary overview - - - Configured Searchers - Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher) - Field values - Health status - The health status of the index and if it can be read - Indexers - Index info - Lists the properties of the index - Manage Examine's indexes - Allows you to view the details of each index and provides some tools for managing the indexes - Rebuild index - - Depending on how much content there is in your site this could take a while.
      - It is not recommended to rebuild an index during times of high website traffic or when editors are editing content. - ]]> -
      - Searchers - Search the index and view the results - Tools - Tools to manage the index - fields - The index cannot be read and will need to be rebuilt - The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation - This index cannot be rebuilt because it has no assigned - IIndexPopulator - - - Enter your username - Enter your password - Confirm your password - Name the %0%... - Enter a name... - Enter an email... - Enter a username... - Label... - Enter a description... - Type to search... - Type to filter... - Type to add tags (press enter after each tag)... - Enter your email - Enter a message... - Your username is usually your email - #value or ?key=value - Enter alias... - Generating alias... - Create item - Create - Edit - Name - - - Create custom list view - Remove custom list view - A content type, media type or member type with this alias already exists - - - Renamed - Enter a new folder name here - %0% was renamed to %1% - - - Add prevalue - Database datatype - Property editor GUID - Property editor - Buttons - Enable advanced settings for - Enable context menu - Maximum default size of inserted images - Related stylesheets - Show label - Width and height - All property types & property data - using this data type will be deleted permanently, please confirm you want to delete these as well - Yes, delete - and all property types & property data using this data type - Select the folder to move - to in the tree structure below - was moved underneath - - - Your data has been saved, but before you can publish this page there are some errors you need to fix first: - The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) - %0% already exists - There were errors: - There were errors: - The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) - %0% must be an integer - The %0% field in the %1% tab is mandatory - %0% is a mandatory field - %0% at %1% is not in a correct format - %0% is not in a correct format - - - Received an error from the server - The specified file type has been disallowed by the administrator - NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. - Please fill both alias and name on the new property type! - There is a problem with read/write access to a specific file or folder - Error loading Partial View script (file: %0%) - Please enter a title - Please choose a type - You're about to make the picture larger than the original size. Are you sure that you want to proceed? - Startnode deleted, please contact your administrator - Please mark content before changing style - No active styles available - Please place cursor at the left of the two cells you wish to merge - You cannot split a cell that hasn't been merged. - This property is invalid - - - About - Action - Actions - Add - Alias - All - Are you sure? - Back - Back to overview - Border - by - Cancel - Cell margin - Choose - Clear - Close - Close Window - Comment - Confirm - Constrain - Constrain proportions - Content - Continue - Copy - Create - Database - Date - Default - Delete - Deleted - Deleting... - Design - Dictionary - Dimensions - Down - Download - Edit - Edited - Elements - Email - Error - Field - Find - First - Focal point - General - Groups - Group - Height - Help - Hide - History - Icon - Id - Import - Include subfolders in search - Search only this folder - Info - Inner margin - Insert - Install - Invalid - Justify - Label - Language - Last - Layout - Links - Loading - Locked - Login - Log off - Logout - Macro - Mandatory - Message - Move - Name - New - Next - No - of - Off - OK - Open - Options - On - or - Order by - Password - Path - One moment please... - Previous - Properties - Rebuild - Email to receive form data - Recycle Bin - Your recycle bin is empty - Reload - Remaining - Remove - Rename - Renew - Required - Retrieve - Retry - Permissions - Scheduled Publishing - Search - Sorry, we can not find what you are looking for. - No items have been added - Server - Settings - Show - Show page on Send - Size - Sort - Status - Submit - Success - Type - Type to search... - under - Up - Update - Upgrade - Upload - Url - User - Username - Value - View - Welcome... - Width - Yes - Folder - Search results - Reorder - I am done reordering - Preview - Change password - to - List view - Saving... - current - Embed - selected - Other - Articles - Videos - Clear - Installing - - - Blue - - - Add group - Add property - Add editor - Add template - Add child node - Add child - Edit data type - Navigate sections - Shortcuts - show shortcuts - Toggle list view - Toggle allow as root - Comment/Uncomment lines - Remove line - Copy Lines Up - Copy Lines Down - Move Lines Up - Move Lines Down - General - Editor - Toggle allow culture variants - Toggle allow segmentation - - - Background colour - Bold - Text colour - Font - Text - - - Page - - - The installer cannot connect to the database. - Could not save the web.config file. Please modify the connection string manually. - Your database has been found and is identified as - Database configuration - - install button to install the Umbraco %0% database - ]]> - - Next to proceed.]]> - Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

      -

      To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

      -

      - Click the retry button when - done.
      - More information on editing web.config here.

      ]]>
      - - Please contact your ISP if necessary. - If you're installing on a local machine or server you might need information from your system administrator.]]> - - Press the upgrade button to upgrade your database to Umbraco %0%

      -

      - Don't worry - no content will be deleted and everything will continue working afterwards! -

      - ]]>
      - Press Next to - proceed. ]]> - next to continue the configuration wizard]]> - The Default users' password needs to be changed!]]> - The Default user has been disabled or has no access to Umbraco!

      No further actions needs to be taken. Click Next to proceed.]]> - The Default user's password has been successfully changed since the installation!

      No further actions needs to be taken. Click Next to proceed.]]> - The password is changed! - Get a great start, watch our introduction videos - By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. - Not installed yet. - Affected files and folders - More information on setting up permissions for Umbraco here - You need to grant ASP.NET modify permissions to the following files/folders - Your permission settings are almost perfect!

      - You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
      - How to Resolve - Click here to read the text version - video tutorial on setting up folder permissions for Umbraco or read the text version.]]> - Your permission settings might be an issue! -

      - You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
      - Your permission settings are not ready for Umbraco! -

      - In order to run Umbraco, you'll need to update your permission settings.]]>
      - Your permission settings are perfect!

      - You are ready to run Umbraco and install packages!]]>
      - Resolving folder issue - Follow this link for more information on problems with ASP.NET and creating folders - Setting up folder permissions - - I want to start from scratch - learn how) - You can still choose to install Runway later on. Please go to the Developer section and choose Packages. - ]]> - You've just set up a clean Umbraco platform. What do you want to do next? - Runway is installed - - This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules - ]]> - Only recommended for experienced users - I want to start with a simple website - - "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, - but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, - Runway offers an easy foundation based on best practices to get you started faster than ever. - If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages. -

      - - Included with Runway: Home page, Getting Started page, Installing Modules page.
      - Optional Modules: Top Navigation, Sitemap, Contact, Gallery. -
      - ]]>
      - What is Runway - Step 1/5 Accept license - Step 2/5: Database configuration - Step 3/5: Validating File Permissions - Step 4/5: Check Umbraco security - Step 5/5: Umbraco is ready to get you started - Thank you for choosing Umbraco - Browse your new site -You installed Runway, so why not see how your new website looks.]]> - Further help and information -Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> - Umbraco %0% is installed and ready for use - /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> - started instantly by clicking the "Launch Umbraco" button below.
      If you are new to Umbraco, -you can find plenty of resources on our getting started pages.]]>
      - Launch Umbraco -To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> - Connection to database failed. - Umbraco Version 3 - Umbraco Version 4 - Watch - Umbraco %0% for a fresh install or upgrading from version 3.0. -

      - Press "next" to start the wizard.]]>
      - - - Culture Code - Culture Name - - - You've been idle and logout will automatically occur in - Renew now to save your work - - - Happy super Sunday - Happy manic Monday - Happy tubular Tuesday - Happy wonderful Wednesday - Happy thunderous Thursday - Happy funky Friday - Happy Caturday - Log in below - Sign in with - Session timed out - © 2001 - %0%
      Umbraco.com

      ]]>
      - Forgotten password? - An email will be sent to the address specified with a link to reset your password - An email with password reset instructions will be sent to the specified address if it matched our records - Show password - Hide password - Return to login form - Please provide a new password - Your Password has been updated - The link you have clicked on is invalid or has expired - Umbraco: Reset Password - - - - - - - - - - - -
      - - - - - -
      - -
      - -
      -
      - - - - - - -
      -
      -
      - - - - -
      - - - - -
      -

      - Password reset requested -

      -

      - Your username to login to the Umbraco back-office is: %0% -

      -

      - - - - - - -
      - - Click this link to reset your password - -
      -

      -

      If you cannot click on the link, copy and paste this URL into your browser window:

      - - - - -
      - - %1% - -
      -

      -
      -
      -


      -
      -
      - - - ]]>
      - - - Dashboard - Sections - Content - - - Choose page above... - %0% has been copied to %1% - Select where the document %0% should be copied to below - %0% has been moved to %1% - Select where the document %0% should be moved to below - has been selected as the root of your new content, click 'ok' below. - No node selected yet, please select a node in the list above before clicking 'ok' - The current node is not allowed under the chosen node because of its type - The current node cannot be moved to one of its subpages - The current node cannot exist at the root - The action isn't allowed since you have insufficient permissions on 1 or more child documents. - Relate copied items to original - - - %0%]]> - Notification settings saved for - - The following languages have been modified %0% - - - - - - - - - - - -
      - - - - - -
      - -
      - -
      -
      - - - - - - -
      -
      -
      - - - - -
      - - - - -
      -

      - Hi %0%, -

      -

      - This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' -

      - - - - - - -
      - -
      - EDIT
      -
      -

      -

      Update summary:

      - %6% -

      -

      - Have a nice day!

      - Cheers from the Umbraco robot -

      -
      -
      -


      -
      -
      - - - ]]>
      - The following languages have been modified:

      - %0% - ]]>
      - [%0%] Notification about %1% performed on %2% - Notifications - - - Actions - Created - Create package - - button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. - ]]> - This will delete the package - Drop to upload - Include all child nodes - or click here to choose package file - Upload package - Install a local package by selecting it from your machine. Only install packages from sources you know and trust - Upload another package - Cancel and upload another package - I accept - terms of use - - Path to file - Absolute path to file (ie: /bin/umbraco.bin) - Installed - Installed packages - Install local - Finish - This package has no configuration view - No packages have been created yet - You don’t have any packages installed - 'Packages' icon in the top right of your screen]]> - Package Actions - Author URL - Package Content - Package Files - Icon URL - Install package - License - License URL - Package Properties - Search for packages - Results for - We couldn’t find anything for - Please try searching for another package or browse through the categories - Popular - New releases - has - karma points - Information - Owner - Contributors - Created - Current version - .NET version - Downloads - Likes - Compatibility - This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100% - External sources - Author - Documentation - Package meta data - Package name - Package doesn't contain any items -
      - You can safely remove this from the system by clicking "uninstall package" below.]]>
      - Package options - Package readme - Package repository - Confirm package uninstall - Package was uninstalled - The package was successfully uninstalled - Uninstall package - - Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, - so uninstall with caution. If in doubt, contact the package author.]]> - Package version - Package already installed - This package cannot be installed, it requires a minimum Umbraco version of - Uninstalling... - Downloading... - Importing... - Installing... - Restarting, please wait... - All done, your browser will now refresh, please wait... - Please click 'Finish' to complete installation and reload the page. - Uploading package... - - - Paste with full formatting (Not recommended) - The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. - Paste as raw text without any formatting at all - Paste, but remove formatting (Recommended) - - - Group based protection - If you want to grant access to all members of specific member groups - You need to create a member group before you can use group based authentication - Error Page - Used when people are logged on, but do not have access - %0%]]> - %0% is now protected]]> - %0%]]> - Login Page - Choose the page that contains the login form - Remove protection... - %0%?]]> - Select the pages that contain login form and error messages - %0%]]> - %0%]]> - Specific members protection - If you wish to grant access to specific members - - - - - - - - - Include unpublished subpages - Publishing in progress - please wait... - %0% out of %1% pages have been published... - %0% has been published - %0% and subpages have been published - Publish %0% and all its subpages - Publish to publish %0% and thereby making its content publicly available.

      - You can publish this page and all its subpages by checking Include unpublished subpages below. - ]]>
      - - - You have not configured any approved colours - - - You can only select items of type(s): %0% - You have picked a content item currently deleted or in the recycle bin - You have picked content items currently deleted or in the recycle bin - - - Deleted item - You have picked a media item currently deleted or in the recycle bin - You have picked media items currently deleted or in the recycle bin - Trashed - - - enter external link - choose internal page - Caption - Link - Open in new window - enter the display caption - Enter the link - - - Reset crop - Save crop - Add new crop - Done - Undo edits - User defined - - + + + + The Umbraco community + https://our.umbraco.com/documentation/Extending-Umbraco/Language-Files + + + Culture and Hostnames + Audit Trail + Browse Node + Change Document Type + Copy + Create + Export + Create Package + Create group + Delete + Disable + Edit settings + Empty recycle bin + Enable + Export Document Type + Import Document Type + Import Package + Edit in Canvas + Exit + Move + Notifications + Public access + Publish + Unpublish + Reload + Republish entire site + Remove + Rename + Restore + Set permissions for the page %0% + Choose where to copy + Choose where to move + to in the tree structure below + Choose where to copy the selected item(s) + Choose where to move the selected item(s) + was moved to + was copied to + was deleted + Permissions + Rollback + Send To Publish + Send To Translation + Set group + Sort + Translate + Update + Set permissions + Unlock + Create Content Template + Resend Invitation + + + Content + Administration + Structure + Other + + + Allow access to assign culture and hostnames + Allow access to view a node's history log + Allow access to view a node + Allow access to change document type for a node + Allow access to copy a node + Allow access to create nodes + Allow access to delete nodes + Allow access to move a node + Allow access to set and change public access for a node + Allow access to publish a node + Allow access to unpublish a node + Allow access to change permissions for a node + Allow access to roll back a node to a previous state + Allow access to send a node for approval before publishing + Allow access to send a node for translation + Allow access to change the sort order for nodes + Allow access to translate a node + Allow access to save a node + Allow access to create a Content Template + + + Content + Info + + + Permission denied. + Add new Domain + remove + Invalid node. + One or more domains have an invalid format. + Domain has already been assigned. + Language + Domain + New domain '%0%' has been created + Domain '%0%' is deleted + Domain '%0%' has already been assigned + Domain '%0%' has been updated + Edit Current Domains + + + Inherit + Culture + + or inherit culture from parent nodes. Will also apply
      + to the current node, unless a domain below applies too.]]> +
      + Domains + + + Clear selection + Select + Do something else + Bold + Cancel Paragraph Indent + Insert form field + Insert graphic headline + Edit Html + Indent Paragraph + Italic + Center + Justify Left + Justify Right + Insert Link + Insert local link (anchor) + Bullet List + Numeric List + Insert macro + Insert picture + Publish and close + Publish with descendants + Edit relations + Return to list + Save + Save and close + Save and publish + Save and schedule + Save and send for approval + Save list view + Schedule + Preview + Save and preview + Preview is disabled because there's no template assigned + Choose style + Show styles + Insert table + Save and generate models + Undo + Redo + Delete tag + Cancel + Confirm + More publishing options + Submit + Submit and close + + + Viewing for + Content deleted + Content unpublished + Content saved and Published + Content saved and published for languages: %0% + Content saved + Content saved for languages: %0% + Content moved + Content copied + Content rolled back + Content sent for publishing + Content sent for publishing for languages: %0% + Sort child items performed by user + %0% + Copy + Publish + Publish + Move + Save + Save + Delete + Unpublish + Rollback + Send To Publish + Send To Publish + Sort + Custom + History (all variants) + + + To change the document type for the selected content, first select from the list of valid types for this location. + Then confirm and/or amend the mapping of properties from the current type to the new, and click Save. + The content has been re-published. + Current Property + Current type + The document type cannot be changed, as there are no alternatives valid for this location. An alternative will be valid if it is allowed under the parent of the selected content item and that all existing child content items are allowed to be created under it. + Document Type Changed + Map Properties + Map to Property + New Template + New Type + none + Content + Select New Document Type + The document type of the selected content has been successfully changed to [new type] and the following properties mapped: + to + Could not complete property mapping as one or more properties have more than one mapping defined. + Only alternate types valid for the current location are displayed. + + + Failed to create a folder under parent with ID %0% + Failed to create a folder under parent with name %0% + The folder name cannot contain illegal characters. + Failed to delete item: %0% + + + Is Published + About this page + Alias + (how would you describe the picture over the phone) + Alternative Links + Click to edit this item + Created by + Original author + Updated by + Created + Date/time this document was created + Document Type + Editing + Remove at + This item has been changed after publication + This item is not published + Last published + There are no items to show + There are no items to show in the list. + No content has been added + No members have been added + Media Type + Link to media item(s) + Member Group + Role + Member Type + No changes have been made + No date chosen + Page title + This media item has no link + Properties + This document is published but is not visible because the parent '%0%' is unpublished + This culture is published but is not visible because it is unpublished on parent '%0%' + This document is published but is not in the cache + Could not get the url + This document is published but its url would collide with content %0% + This document is published but its url cannot be routed + Publish + Published + Published (pending changes) + Publication Status + %0% and all content items underneath and thereby making their content publicly available.]]> + + Publish at + Unpublish at + Clear Date + Set date + Sortorder is updated + To sort the nodes, simply drag the nodes or click one of the column headers. You can select multiple nodes by holding the "shift" or "control" key while selecting + Statistics + Title (optional) + Alternative text (optional) + Type + Unpublish + Unpublished + Last edited + Date/time this document was edited + Remove file(s) + Click here to remove the image from the media item + Click here to remove the file from the media item + Link to document + Member of group(s) + Not a member of group(s) + Child items + Target + This translates to the following time on the server: + What does this mean?]]> + Are you sure you want to delete this item? + Property %0% uses editor %1% which is not supported by Nested Content. + Are you sure you want to delete all items? + No content types are configured for this property. + Add element type + Select element type + Select the group whose properties should be displayed. If left blank, the first group on the element type will be used. + Enter an angular expression to evaluate against each item for its name. Use + to display the item index + Add another text box + Remove this text box + Content root + Include 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! + What languages would you like to publish? + What languages would you like to save? + All languages with content are saved on creation! + What languages would you like to send for approval? + What languages would you like to schedule? + Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages. + Published Languages + Unpublished Languages + Unmodified Languages + These languages haven't been created + + 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. + Create new + Paste from clipboard + This item is in the Recycle Bin + + + Create a new Content Template from '%0%' + Blank + Select a Content Template + Content Template created + A Content Template was created from '%0%' + Another Content Template with the same name already exists + A Content Template is predefined content that an editor can select to use as the basis for creating new content + + + Click to upload + or click here to choose files + You can drag files here to upload + Cannot upload this file, it does not have an approved file type + Max file size is + Media root + Failed to move media + Failed to copy media + Failed to create a folder under parent id %0% + Failed to rename the folder with id %0% + Drag and drop your file(s) into the area + + + Create a new member + All Members + Member groups have no additional properties for editing. + + + Where do you want to create the new %0% + Create an item under + Select the document type you want to make a content template for + Enter a folder name + Choose a type and a title + Document Types within the Settings section, by editing the Allowed child node types under Permissions.]]> + Document Types within the Settings section.]]> + The selected page in the content tree doesn't allow for any pages to be created below it. + Edit permissions for this document type + Create a new document type + 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 + New folder + New data type + New JavaScript file + New empty partial view + New partial view macro + New partial view from snippet + New partial view macro from snippet + New partial view macro (without macro) + New style sheet file + New Rich Text Editor style sheet file + + + Browse your website + - Hide + If Umbraco isn't opening, you might need to allow popups from this site + has opened in a new window + Restart + Visit + Welcome + + + Stay + Discard changes + You have unsaved changes + Are you sure you want to navigate away from this page? - you have unsaved changes + Publishing will make the selected items visible on the site. + Unpublishing will remove the selected items and all their descendants from the site. + Unpublishing will remove this page and all its descendants from the site. + You have unsaved changes. Making changes to the Document Type will discard the changes. + + + Done + Deleted %0% item + Deleted %0% items + Deleted %0% out of %1% item + Deleted %0% out of %1% items + Published %0% item + Published %0% items + Published %0% out of %1% item + Published %0% out of %1% items + Unpublished %0% item + Unpublished %0% items + Unpublished %0% out of %1% item + Unpublished %0% out of %1% items + Moved %0% item + Moved %0% items + Moved %0% out of %1% item + Moved %0% out of %1% items + Copied %0% item + Copied %0% items + Copied %0% out of %1% item + Copied %0% out of %1% items + + + Link title + Link + Anchor / querystring + Name + Manage hostnames + Close this window + Are you sure you want to delete + Are you sure you want to delete %0% based on %1% + Are you sure you want to disable + Are you sure you want to remove + %0%]]> + %0%]]> + Are you sure? + Are you sure? + Cut + Edit Dictionary Item + Edit Language + Edit selected media + Insert local link + Insert character + Insert graphic headline + Insert picture + Insert link + Click to add a Macro + Insert table + This will delete the language + Changing the culture for a language may be an expensive operation and will result in the content cache and indexes being rebuilt + Last Edited + Link + Internal link: + When using local links, insert "#" in front of link + Open in new window? + Macro Settings + This macro does not contain any properties you can edit + Paste + Edit permissions for + Set permissions for + Set permissions for %0% for user group %1% + Select the users groups you want to set permissions for + The items in the recycle bin are now being deleted. Please do not close this window while this operation takes place + The recycle bin is now empty + When items are deleted from the recycle bin, they will be gone forever + regexlib.com's webservice is currently experiencing some problems, which we have no control over. We are very sorry for this inconvenience.]]> + Search for a regular expression to add validation to a form field. Example: 'email, 'zip-code' 'url' + Remove Macro + Required Field + Site is reindexed + The website cache has been refreshed. All publish content is now up to date. While all unpublished content is still unpublished + The website cache will be refreshed. All published content will be updated, while unpublished content will stay unpublished. + Number of columns + Number of rows + Click on the image to see full size + Pick item + View Cache Item + Relate to original + Include descendants + The friendliest community + Link to page + Opens the linked document in a new window or tab + Link to media + Select content start node + Select media + Select media type + Select icon + Select item + Select link + Select macro + Select content + Select content type + Select media start node + Select member + Select member group + Select member type + Select node + Select sections + Select user + Select users + No icons were found + There are no parameters for this macro + There are no macros available to insert + External login providers + Exception Details + Stacktrace + Inner Exception + Link your + Un-link your + account + Select editor + 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. + + + There are no dictionary items. + + + %0%' below + ]]> + Culture Name + + Dictionary overview + + + Configured Searchers + Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher) + Field values + Health status + The health status of the index and if it can be read + Indexers + Index info + Lists the properties of the index + Manage Examine's indexes + Allows you to view the details of each index and provides some tools for managing the indexes + Rebuild index + + Depending on how much content there is in your site this could take a while.
      + It is not recommended to rebuild an index during times of high website traffic or when editors are editing content. + ]]> +
      + Searchers + Search the index and view the results + Tools + Tools to manage the index + fields + The index cannot be read and will need to be rebuilt + The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation + This index cannot be rebuilt because it has no assigned + IIndexPopulator + + + Enter your username + Enter your password + Confirm your password + Name the %0%... + Enter a name... + Enter an email... + Enter a username... + Label... + Enter a description... + Type to search... + Type to filter... + Type to add tags (press enter after each tag)... + Enter your email + Enter a message... + Your username is usually your email + #value or ?key=value + Enter alias... + Generating alias... + Create item + Create + Edit + Name + + + Create custom list view + Remove custom list view + A content type, media type or member type with this alias already exists + + + Renamed + Enter a new folder name here + %0% was renamed to %1% + + + Add prevalue + Database datatype + Property editor GUID + Property editor + Buttons + Enable advanced settings for + Enable context menu + Maximum default size of inserted images + Related stylesheets + Show label + Width and height + All property types & property data + using this data type will be deleted permanently, please confirm you want to delete these as well + Yes, delete + and all property types & property data using this data type + Select the folder to move + to in the tree structure below + was moved underneath + + + Your data has been saved, but before you can publish this page there are some errors you need to fix first: + The current membership provider does not support changing password (EnablePasswordRetrieval need to be true) + %0% already exists + There were errors: + There were errors: + The password should be a minimum of %0% characters long and contain at least %1% non-alpha numeric character(s) + %0% must be an integer + The %0% field in the %1% tab is mandatory + %0% is a mandatory field + %0% at %1% is not in a correct format + %0% is not in a correct format + Property '%0%' uses editor '%1%' which is not supported in Element Types. + + + Received an error from the server + The specified file type has been disallowed by the administrator + NOTE! Even though CodeMirror is enabled by configuration, it is disabled in Internet Explorer because it's not stable enough. + Please fill both alias and name on the new property type! + There is a problem with read/write access to a specific file or folder + Error loading Partial View script (file: %0%) + Please enter a title + Please choose a type + You're about to make the picture larger than the original size. Are you sure that you want to proceed? + Startnode deleted, please contact your administrator + Please mark content before changing style + No active styles available + Please place cursor at the left of the two cells you wish to merge + You cannot split a cell that hasn't been merged. + This property is invalid + + + About + Action + Actions + Add + Alias + All + Are you sure? + Back + Back to overview + Border + by + Cancel + Cell margin + Choose + Clear + Close + Close Window + Comment + Confirm + Constrain + Constrain proportions + Content + Continue + Copy + Create + Database + Date + Default + Delete + Deleted + Deleting... + Design + Dictionary + Dimensions + Down + Download + Edit + Edited + Elements + Email + Error + Field + Find + First + Focal point + General + Groups + Group + Height + Help + Hide + History + Icon + Id + Import + Include subfolders in search + Search only this folder + Info + Inner margin + Insert + Install + Invalid + Justify + Label + Language + Last + Layout + Links + Loading + Locked + Login + Log off + Logout + Macro + Mandatory + Message + Move + Name + New + Next + No + of + Off + OK + Open + Options + On + or + Order by + Password + Path + One moment please... + Previous + Properties + Rebuild + Email to receive form data + Recycle Bin + Your recycle bin is empty + Reload + Remaining + Remove + Rename + Renew + Required + Retrieve + Retry + Permissions + Scheduled Publishing + Search + Sorry, we can not find what you are looking for. + No items have been added + Server + Settings + Show + Show page on Send + Size + Sort + Status + Submit + Success + Type + Type to search... + under + Up + Update + Upgrade + Upload + Url + User + Username + Value + View + Welcome... + Width + Yes + Folder + Search results + Reorder + I am done reordering + Preview + Change password + to + List view + Saving... + current + Embed + selected + Other + Articles + Videos + Clear + Installing + + + Blue + + + Add group + Add property + Add editor + Add template + Add child node + Add child + Edit data type + Navigate sections + Shortcuts + show shortcuts + Toggle list view + Toggle allow as root + Comment/Uncomment lines + Remove line + Copy Lines Up + Copy Lines Down + Move Lines Up + Move Lines Down + General + Editor + Toggle allow culture variants + Toggle allow segmentation + + + Background colour + Bold + Text colour + Font + Text + + + Page + + + The installer cannot connect to the database. + Could not save the web.config file. Please modify the connection string manually. + Your database has been found and is identified as + Database configuration + + install button to install the Umbraco %0% database + ]]> + + Next to proceed.]]> + Database not found! Please check that the information in the "connection string" of the "web.config" file is correct.

      +

      To proceed, please edit the "web.config" file (using Visual Studio or your favourite text editor), scroll to the bottom, add the connection string for your database in the key named "UmbracoDbDSN" and save the file.

      +

      + Click the retry button when + done.
      + More information on editing web.config here.

      ]]>
      + + Please contact your ISP if necessary. + If you're installing on a local machine or server you might need information from your system administrator.]]> + + Press the upgrade button to upgrade your database to Umbraco %0%

      +

      + Don't worry - no content will be deleted and everything will continue working afterwards! +

      + ]]>
      + Press Next to + proceed. ]]> + next to continue the configuration wizard]]> + The Default users' password needs to be changed!]]> + The Default user has been disabled or has no access to Umbraco!

      No further actions needs to be taken. Click Next to proceed.]]> + The Default user's password has been successfully changed since the installation!

      No further actions needs to be taken. Click Next to proceed.]]> + The password is changed! + Get a great start, watch our introduction videos + By clicking the next button (or modifying the umbracoConfigurationStatus in web.config), you accept the license for this software as specified in the box below. Notice that this Umbraco distribution consists of two different licenses, the open source MIT license for the framework and the Umbraco freeware license that covers the UI. + Not installed yet. + Affected files and folders + More information on setting up permissions for Umbraco here + You need to grant ASP.NET modify permissions to the following files/folders + Your permission settings are almost perfect!

      + You can run Umbraco without problems, but you will not be able to install packages which are recommended to take full advantage of Umbraco.]]>
      + How to Resolve + Click here to read the text version + video tutorial on setting up folder permissions for Umbraco or read the text version.]]> + Your permission settings might be an issue! +

      + You can run Umbraco without problems, but you will not be able to create folders or install packages which are recommended to take full advantage of Umbraco.]]>
      + Your permission settings are not ready for Umbraco! +

      + In order to run Umbraco, you'll need to update your permission settings.]]>
      + Your permission settings are perfect!

      + You are ready to run Umbraco and install packages!]]>
      + Resolving folder issue + Follow this link for more information on problems with ASP.NET and creating folders + Setting up folder permissions + + I want to start from scratch + learn how) + You can still choose to install Runway later on. Please go to the Developer section and choose Packages. + ]]> + You've just set up a clean Umbraco platform. What do you want to do next? + Runway is installed + + This is our list of recommended modules, check off the ones you would like to install, or view the full list of modules + ]]> + Only recommended for experienced users + I want to start with a simple website + + "Runway" is a simple website providing some basic document types and templates. The installer can set up Runway for you automatically, + but you can easily edit, extend or remove it. It's not necessary and you can perfectly use Umbraco without it. However, + Runway offers an easy foundation based on best practices to get you started faster than ever. + If you choose to install Runway, you can optionally select basic building blocks called Runway Modules to enhance your Runway pages. +

      + + Included with Runway: Home page, Getting Started page, Installing Modules page.
      + Optional Modules: Top Navigation, Sitemap, Contact, Gallery. +
      + ]]>
      + What is Runway + Step 1/5 Accept license + Step 2/5: Database configuration + Step 3/5: Validating File Permissions + Step 4/5: Check Umbraco security + Step 5/5: Umbraco is ready to get you started + Thank you for choosing Umbraco + Browse your new site +You installed Runway, so why not see how your new website looks.]]> + Further help and information +Get help from our award winning community, browse the documentation or watch some free videos on how to build a simple site, how to use packages and a quick guide to the Umbraco terminology]]> + Umbraco %0% is installed and ready for use + /web.config file and update the AppSetting key UmbracoConfigurationStatus in the bottom to the value of '%0%'.]]> + started instantly by clicking the "Launch Umbraco" button below.
      If you are new to Umbraco, +you can find plenty of resources on our getting started pages.]]>
      + Launch Umbraco +To manage your website, simply open the Umbraco back office and start adding content, updating the templates and stylesheets or add new functionality]]> + Connection to database failed. + Umbraco Version 3 + Umbraco Version 4 + Watch + Umbraco %0% for a fresh install or upgrading from version 3.0. +

      + Press "next" to start the wizard.]]>
      + + + Culture Code + Culture Name + + + You've been idle and logout will automatically occur in + Renew now to save your work + + + Happy super Sunday + Happy manic Monday + Happy tubular Tuesday + Happy wonderful Wednesday + Happy thunderous Thursday + Happy funky Friday + Happy Caturday + Log in below + Sign in with + Session timed out + © 2001 - %0%
      Umbraco.com

      ]]>
      + Forgotten password? + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records + Show password + Hide password + Return to login form + Please provide a new password + Your Password has been updated + The link you have clicked on is invalid or has expired + Umbraco: Reset Password + + + + + + + + + + + +
      + + + + + +
      + +
      + +
      +
      + + + + + + +
      +
      +
      + + + + +
      + + + + +
      +

      + Password reset requested +

      +

      + Your username to login to the Umbraco back-office is: %0% +

      +

      + + + + + + +
      + + Click this link to reset your password + +
      +

      +

      If you cannot click on the link, copy and paste this URL into your browser window:

      + + + + +
      + + %1% + +
      +

      +
      +
      +


      +
      +
      + + + ]]>
      + + + Dashboard + Sections + Content + + + Choose page above... + %0% has been copied to %1% + Select where the document %0% should be copied to below + %0% has been moved to %1% + Select where the document %0% should be moved to below + has been selected as the root of your new content, click 'ok' below. + No node selected yet, please select a node in the list above before clicking 'ok' + The current node is not allowed under the chosen node because of its type + The current node cannot be moved to one of its subpages + The current node cannot exist at the root + The action isn't allowed since you have insufficient permissions on 1 or more child documents. + Relate copied items to original + + + %0%]]> + Notification settings saved for + + The following languages have been modified %0% + + + + + + + + + + + +
      + + + + + +
      + +
      + +
      +
      + + + + + + +
      +
      +
      + + + + +
      + + + + +
      +

      + Hi %0%, +

      +

      + This is an automated mail to inform you that the task '%1%' has been performed on the page '%2%' by the user '%3%' +

      + + + + + + +
      + +
      + EDIT
      +
      +

      +

      Update summary:

      + %6% +

      +

      + Have a nice day!

      + Cheers from the Umbraco robot +

      +
      +
      +


      +
      +
      + + + ]]>
      + The following languages have been modified:

      + %0% + ]]>
      + [%0%] Notification about %1% performed on %2% + Notifications + + + Actions + Created + Create package + + button and locating the package. Umbraco packages usually have a ".umb" or ".zip" extension. + ]]> + This will delete the package + Drop to upload + Include all child nodes + or click here to choose package file + Upload package + Install a local package by selecting it from your machine. Only install packages from sources you know and trust + Upload another package + Cancel and upload another package + I accept + terms of use + + Path to file + Absolute path to file (ie: /bin/umbraco.bin) + Installed + Installed packages + Install local + Finish + This package has no configuration view + No packages have been created yet + You don’t have any packages installed + 'Packages' icon in the top right of your screen]]> + Package Actions + Author URL + Package Content + Package Files + Icon URL + Install package + License + License URL + Package Properties + Search for packages + Results for + We couldn’t find anything for + Please try searching for another package or browse through the categories + Popular + New releases + has + karma points + Information + Owner + Contributors + Created + Current version + .NET version + Downloads + Likes + Compatibility + This package is compatible with the following versions of Umbraco, as reported by community members. Full compatability cannot be guaranteed for versions reported below 100% + External sources + Author + Documentation + Package meta data + Package name + Package doesn't contain any items +
      + You can safely remove this from the system by clicking "uninstall package" below.]]>
      + Package options + Package readme + Package repository + Confirm package uninstall + Package was uninstalled + The package was successfully uninstalled + Uninstall package + + Notice: any documents, media etc depending on the items you remove, will stop working, and could lead to system instability, + so uninstall with caution. If in doubt, contact the package author.]]> + Package version + Package already installed + This package cannot be installed, it requires a minimum Umbraco version of + Uninstalling... + Downloading... + Importing... + Installing... + Restarting, please wait... + All done, your browser will now refresh, please wait... + Please click 'Finish' to complete installation and reload the page. + Uploading package... + + + Paste with full formatting (Not recommended) + The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Umbraco can remove special characters or formatting automatically, so the pasted content will be more suitable for the web. + Paste as raw text without any formatting at all + Paste, but remove formatting (Recommended) + + + Group based protection + If you want to grant access to all members of specific member groups + You need to create a member group before you can use group based authentication + Error Page + Used when people are logged on, but do not have access + %0%]]> + %0% is now protected]]> + %0%]]> + Login Page + Choose the page that contains the login form + Remove protection... + %0%?]]> + Select the pages that contain login form and error messages + %0%]]> + %0%]]> + Specific members protection + If you wish to grant access to specific members + + + + + + + + + Include unpublished subpages + Publishing in progress - please wait... + %0% out of %1% pages have been published... + %0% has been published + %0% and subpages have been published + Publish %0% and all its subpages + Publish to publish %0% and thereby making its content publicly available.

      + You can publish this page and all its subpages by checking Include unpublished subpages below. + ]]>
      + + + You have not configured any approved colours + + + You can only select items of type(s): %0% + You have picked a content item currently deleted or in the recycle bin + You have picked content items currently deleted or in the recycle bin + + + Deleted item + You have picked a media item currently deleted or in the recycle bin + You have picked media items currently deleted or in the recycle bin + Trashed + + + enter external link + choose internal page + Caption + Link + Open in new window + enter the display caption + Enter the link + + + Reset crop + Save crop + Add new crop + Done + Undo edits + User defined + + Changes Created - Current version - Red text will not be shown in the selected version. , green means added]]> - Document has been rolled back + Current version + Red text will not be shown in the selected version. , green means added]]> + Document has been rolled back Select a version to compare with the current version - This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view - Rollback to - Select version - View - - - Edit script file - - - Concierge - Content - Courier - Developer - Forms - Help - Umbraco Configuration Wizard - Media - Members - Newsletters - Packages - Settings - Statistics - Translation - Users - - - Tours - The best Umbraco video tutorials - Visit our.umbraco.com - Visit umbraco.tv - - - Default template - To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) - New Tab Title - Node type - Type - Stylesheet - Script - Tab - Tab Title - Tabs - Master Content Type enabled - This Content Type uses - as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself - No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. - Create matching template - Add icon - - - Sort order - Creation date - Sorting complete. - Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items - - - - Validation - Validation errors must be fixed before the item can be saved - Failed - Saved - Insufficient user permissions, could not complete the operation - Cancelled - Operation was cancelled by a 3rd party add-in - Publishing was cancelled by a 3rd party add-in - Property type already exists - Property type created - DataType: %1%]]> - Propertytype deleted - Document Type saved - Tab created - Tab deleted - Tab with id: %0% deleted - Stylesheet not saved - Stylesheet saved - Stylesheet saved without any errors - Datatype saved - Dictionary item saved - Publishing failed because the parent page isn't published - Content published - and visible on the website - Content saved - Remember to publish to make changes visible - Sent For Approval - Changes have been sent for approval - Media saved - Member group saved - Media saved without any errors - Member saved - Stylesheet Property Saved - Stylesheet saved - Template saved - Error saving user (check log) - User Saved - User type saved - User group saved - Cultures and hostnames saved - Error saving cultures and hostnames - File not saved - file could not be saved. Please check file permissions - File saved - File saved without any errors - Language saved - Media Type saved - Member Type saved - Member Group saved - Template not saved - Please make sure that you do not have 2 templates with the same alias - Template saved - Template saved without any errors! - Content unpublished - Partial view saved - Partial view saved without any errors! - Partial view not saved - An error occurred saving the file. - Permissions saved for - Deleted %0% user groups - %0% was deleted - Enabled %0% users - Disabled %0% users - %0% is now enabled - %0% is now disabled - User groups have been set - Unlocked %0% users - %0% is now unlocked - Member was exported to file - An error occurred while exporting the member - User %0% was deleted - Invite user - Invitation has been re-sent to %0% - Document type was exported to file - An error occurred while exporting the document type - - - Add style - Edit style - Rich text editor styles - Define the styles that should be available in the rich text editor for this stylesheet - Edit stylesheet - Edit stylesheet property - The name displayed in the editor style selector - Preview - How the text will look like in the rich text editor. - Selector - Uses CSS syntax, e.g. "h1" or ".redHeader" - Styles - The CSS that should be applied in the rich text editor, e.g. "color:red;" - Code - Editor - - - Failed to delete template with ID %0% - Edit template - Sections - Insert content area - Insert content area placeholder - Insert - Choose what to insert into your template - Dictionary item - A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. - Macro - - A Macro is a configurable component which is great for - reusable parts of your design, where you need the option to provide parameters, - such as galleries, forms and lists. - - Value - Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. - Partial view - - A partial view is a separate template file which can be rendered inside another - template, it's great for reusing markup or for separating complex templates into separate files. - - Master template - No master - Render child template - @RenderBody() placeholder. - ]]> - Define a named section - @section { ... }. This can be rendered in a - specific area of the parent of this template, by using @RenderSection. - ]]> - Render a named section - @RenderSection(name) placeholder. - This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. - ]]> - Section Name - Section is mandatory - - If mandatory, the child template must contain a @section definition, otherwise an error is shown. - - Query builder - items returned, in - copy to clipboard - I want - all content - content of type "%0%" - from - my website - where - and - is - is not - before - before (including selected date) - after - after (including selected date) - equals - does not equal - contains - does not contain - greater than - greater than or equal to - less than - less than or equal to - Id - Name - Created Date - Last Updated Date - order by - ascending - descending - Template - - - Image - Macro - Choose type of content - Choose a layout - Add a row - Add content - Drop content - Settings applied - This content is not allowed here - This content is allowed here - Click to embed - Click to insert image - Image caption... - Write here... - Grid Layouts - Layouts are the overall work area for the grid editor, usually you only need one or two different layouts - Add Grid Layout - Adjust the layout by setting column widths and adding additional sections - Row configurations - Rows are predefined cells arranged horizontally - Add row configuration - Adjust the row by setting cell widths and adding additional cells - Columns - Total combined number of columns in the grid layout - Settings - Configure what settings editors can change - Styles - Configure what styling editors can change - Allow all editors - Allow all row configurations - Maximum items - Leave blank or set to 0 for unlimited - Set as default - Choose extra - Choose default - are added - Warning - You are deleting the row configuration - - Deleting a row configuration name will result in loss of data for any existing content that is based on this configuration. - - - - Compositions - Group - You have not added any groups - Add group - Inherited from - Add property - Required label - Enable list view - Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree - Allowed Templates - Choose which templates editors are allowed to use on content of this type - Allow as root - Allow editors to create content of this type in the root of the content tree. - Allowed child node types - Allow content of the specified types to be created underneath content of this type. - Choose child node - Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. - This content type is used in a composition, and therefore cannot be composed itself. - There are no content types available to use as a composition. - Removing a composition will delete all the associated property data. Once you save the document type there's no way back. - Create new - Use existing - Editor settings - Configuration - Yes, delete - was moved underneath - was copied underneath - Select the folder to move - Select the folder to copy - to in the tree structure below - All Document types - All Documents - All media items - using this document type will be deleted permanently, please confirm you want to delete these as well. - using this media type will be deleted permanently, please confirm you want to delete these as well. - using this member type will be deleted permanently, please confirm you want to delete these as well - and all documents using this type - and all media items using this type - and all members using this type - Member can edit - Allow this property value to be edited by the member on their profile page - Is sensitive data - Hide this property value from content editors that don't have access to view sensitive information - Show on member profile - Allow this property value to be displayed on the member profile page - tab has no sort order - Where is this composition used? - This composition is currently used in the composition of the following content types: - 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. - A document type cannot be changed to an Element type once it has been used to create one or more content items. - This is not applicable for an Element type - You have made changes to this property. Are you sure you want to discard them? - - - Add language - Mandatory language - Properties on this language have to be filled out before the node can be published. - Default language - An Umbraco site can only have one default language set. - Switching default language may result in default content missing. - Falls back to - No fall back language - To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. - Fall back language - none - - - - Add parameter - Edit parameter - Enter macro name - Parameters - Define the parameters that should be available when using this macro. - Select partial view macro file - - - Building models - this can take a bit of time, don't worry - Models generated - Models could not be generated - Models generation has failed, see exception in U log - - - Add fallback field - Fallback field - Add default value - Default value - Fallback field - Default value - Casing - Encoding - Choose field - Convert line breaks - Yes, convert line breaks - Replaces line breaks with 'br' html tag - Custom Fields - Date only - Format and encoding - Format as date - Format the value as a date, or a date with time, according to the active culture - HTML encode - Will replace special characters by their HTML equivalent. - Will be inserted after the field value - Will be inserted before the field value - Lowercase - Modify output - None - Output sample - Insert after field - Insert before field - Recursive - Yes, make it recursive - Separator - Standard Fields - Uppercase - URL encode - Will format special characters in URLs - Will only be used when the field values above are empty - This field will only be used if the primary field is empty - Date and time - - - Translation details - Download XML DTD - Fields - Include subpages - - No translator users found. Please create a translator user before you start sending content to translation - The page '%0%' has been send to translation - Send the page '%0%' to translation - Total words - Translate to - Translation completed. - You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. - Translation failed, the XML file might be corrupt - Translation options - Translator - Upload translation XML - - - Content - Content Templates - Media - Cache Browser - Recycle Bin - Created packages - Data Types - Dictionary - Installed packages - Install skin - Install starter kit - Languages - Install local package - Macros - Media Types - Members - Member Groups - Member Roles - Member Types - Document Types - Relation Types - Packages - Packages - Partial Views - Partial View Macro Files - Install from repository - Install Runway - Runway modules - Scripting Files - Scripts - Stylesheets - Templates - Log Viewer - Users - Settings - Templating - Third Party - - - New update ready - %0% is ready, click here for download - No connection to server - Error checking for update. Please review trace-stack for further information - - - Access - Based on the assigned groups and start nodes, the user has access to the following nodes - Assign access - Administrator - Category field - User created - Change Your Password - Change photo - New password - hasn't been locked out - The password hasn't been changed - Confirm new password - You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button - Content Channel - Create another user - Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user. - Description field - Disable User - Document Type - Editor - Excerpt field - Failed login attempts - Go to user profile - Add groups to assign access and permissions - Invite another user - Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours. - Language - Set the language you will see in menus and dialogs - Last lockout date - Last login - Password last changed - Username - Media start node - Limit the media library to a specific start node - Media start nodes - Limit the media library to specific start nodes - Sections - Disable Umbraco Access - has not logged in yet - Old password - Password - Reset password - Your password has been changed! - Password changed - Please confirm the new password - Enter your new password - Your new password cannot be blank! - Current password - Invalid current password - There was a difference between the new password and the confirmed password. Please try again! - The confirmed password doesn't match the new password! - Replace child node permissions - You are currently modifying permissions for the pages: - Select pages to modify their permissions - Remove photo - Default permissions - Granular permissions - Set permissions for specific nodes - Profile - Search all children - Add sections to give users access - Select user groups - No start node selected - No start nodes selected - Content start node - Limit the content tree to a specific start node - Content start nodes - Limit the content tree to specific start nodes - User last updated - has been created - The new user has successfully been created. To log in to Umbraco use the password below. - User management - Name - User permissions - User group - has been invited - An invitation has been sent to the new user with details about how to log in to Umbraco. - Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. - Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. - Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. - Writer - Change - Your profile - Your recent history - Session expires in - Invite user - Create user - Send invite - Back to users - Umbraco: Invitation - - - - - - - - - - - -
      - - - - - -
      - -
      - -
      -
      - - - - - - -
      -
      -
      - - - - -
      - - - - -
      -

      - Hi %0%, -

      -

      - You have been invited by %1% to the Umbraco Back Office. -

      -

      - Message from %1%: -
      - %2% -

      - - - - - - -
      - - - - - - -
      - - Click this link to accept the invite - -
      -
      -

      If you cannot click on the link, copy and paste this URL into your browser window:

      - - - - -
      - - %3% - -
      -

      -
      -
      -


      -
      -
      - - ]]>
      - Invite - Resending invitation... - Delete User - Are you sure you wish to delete this user account? - All - Active - Disabled - Locked out - Invited - Inactive - Name (A-Z) - Name (Z-A) - Newest - Oldest - Last login - No user groups have been added - - - Validation - No validation - Validate as an email address - Validate as a number - Validate as a URL - ...or enter a custom validation - Field is mandatory - Enter a custom validation error message (optional) - Enter a regular expression - Enter a custom validation error message (optional) - You need to add at least - You can only have - Add up to - items - url(s) - url(s) selected - items selected - Invalid date - Not a number - Invalid email - Custom validation - %1% more.]]> - %1% too many.]]> - - - - Value is set to the recommended value: '%0%'. - Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. - Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. - Found unexpected value '%0%' for '%2%' in configuration file '%3%'. - - Custom errors are set to '%0%'. - Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. - Custom errors successfully set to '%0%'. - MacroErrors are set to '%0%'. - MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. - MacroErrors are now set to '%0%'. - - Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. - Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). - Try Skip IIS Custom Errors successfully set to '%0%'. - - File does not exist: '%0%'. - '%0%' in config file '%1%'.]]> - There was an error, check log for full error: %0%. - Database - The database schema is correct for this version of Umbraco - %0% problems were detected with your database schema (Check the log for details) - Some errors were detected while validating the database schema against the current version of Umbraco. - Your website's certificate is valid. - Certificate validation error: '%0%' - Your website's SSL certificate has expired. - Your website's SSL certificate is expiring in %0% days. - Error pinging the URL %0% - '%1%' - You are currently %0% viewing the site using the HTTPS scheme. - The appSetting 'Umbraco.Core.UseHttps' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. - The appSetting 'Umbraco.Core.UseHttps' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. - Could not update the 'Umbraco.Core.UseHttps' setting in your web.config file. Error: %0% - - Enable HTTPS - Sets umbracoSSL setting to true in the appSettings of the web.config file. - The appSetting 'Umbraco.Core.UseHttps' is now set to 'true' in your web.config file, your cookies will be marked as secure. - Fix - Cannot fix a check with a value comparison type of 'ShouldNotEqual'. - Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. - Value to fix check not provided. - Debug compilation mode is disabled. - Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. - Debug compilation mode successfully disabled. - Trace mode is disabled. - Trace mode is currently enabled. It is recommended to disable this setting before go live. - Trace mode successfully disabled. - All folders have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> - All files have the correct permissions set. - - %0%.]]> - %0%. If they aren't being written to no action need be taken.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> - X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> - Set Header in Config - Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. - A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. - Could not update web.config file. Error: %0% - X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was found.]]> - X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was not found.]]> - Adds a value to the httpProtocol/customHeaders section of web.config to protect against MIME sniffing vulnerabilities. - A setting to create a header protecting against MIME sniffing vulnerabilities has been added to your web.config file. - Strict-Transport-Security, also known as the HSTS-header, was found.]]> - Strict-Transport-Security was not found.]]> - Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). - The HSTS header has been added to your web.config file. - X-XSS-Protection was found.]]> - X-XSS-Protection was not found.]]> - Adds the header 'X-XSS-Protection' with the value '1; mode=block' to the httpProtocol/customHeaders section of web.config. - The X-XSS-Protection header has been added to your web.config file. - - %0%.]]> - No headers revealing information about the website technology were found. - In the Web.config file, system.net/mailsettings could not be found. - In the Web.config file system.net/mailsettings section, the host is not configured. - SMTP settings are configured correctly and the service is operating as expected. - The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. - %0%.]]> - %0%.]]> -

      Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

      %2%]]>
      - Umbraco Health Check Status: %0% - Check All Groups - Check group - - The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. - You can add your own health checks, have a look at the documentation for more information about custom health checks.

      - ]]> -
      - - - Disable URL tracker - Enable URL tracker - Original URL - Redirected To - Redirect Url Management - The following URLs redirect to this content item: - No redirects have been made - When a published page gets renamed or moved a redirect will automatically be made to the new page. - Are you sure you want to remove the redirect from '%0%' to '%1%'? - Redirect URL removed. - Error removing redirect URL. - This will remove the redirect - Are you sure you want to disable the URL tracker? - URL tracker has now been disabled. - Error disabling the URL tracker, more information can be found in your log file. - URL tracker has now been enabled. - Error enabling the URL tracker, more information can be found in your log file. - - - No Dictionary items to choose from - - - %0% characters left.]]> - %1% too many.]]> - - - Trashed content with Id: {0} related to original parent content with Id: {1} - Trashed media with Id: {0} related to original parent media item with Id: {1} - Cannot automatically restore this item - There is no location where this item can be automatically restored. You can move the item manually using the tree below. - was restored under - - - Direction - Parent to child - Bidirectional - Parent - Child - Count - Relations - Created - Comment - Name - No relations for this relation type. - Relation Type - Relations - - - Getting Started - Redirect URL Management - Content - Welcome - Examine Management - Published Status - Models Builder - Health Check - Profiling - Getting Started - Install Umbraco Forms - - - Go back - Active layout: - Jump to - group - passed - warning - failed - suggestion - Check passed - Check failed - 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 - Create new folder - Partial View - Partial View Macro - Member - Data type - Search the redirect dashboard - Search the user group section - Search the users section - Create item - Create - Edit - Name - Add new row - View more options - Has translation - Missing translation - Dictionary items - - - References - This Data Type has no references. - Used in Document Types - No references to Document Types. - Used in Media Types - No references to Media Types. - Used in Member Types - No references to Member Types. - Used by - Used in Documents - Used in Members - Used in Media + This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view + Rollback to + Select version + View - - Delete Saved Search - Log Levels - Saved Searches - Save Search - Enter a friendly name for your search query - Filter Search - Total Items - Timestamp - Level - Machine - Message - Exception - Properties - Search With Google - Search this message with Google - Search With Bing - Search this message with Bing - Search Our Umbraco - Search this message on Our Umbraco forums and docs - Search Our Umbraco with Google - Search Our Umbraco forums using Google - Search Umbraco Source - Search within Umbraco source code on Github - Search Umbraco Issues - Search Umbraco Issues on Github - Delete this search - Find Logs with Request ID - Find Logs with Namespace - Find Logs with Machine Name - Open - - - Copy %0% - %0% from %1% - Remove all items - - + + Edit script file + + + Concierge + Content + Courier + Developer + Forms + Help + Umbraco Configuration Wizard + Media + Members + Newsletters + Packages + Settings + Statistics + Translation + Users + + + Tours + The best Umbraco video tutorials + Visit our.umbraco.com + Visit umbraco.tv + + + Default template + To import a document type, find the ".udt" file on your computer by clicking the "Browse" button and click "Import" (you'll be asked for confirmation on the next screen) + New Tab Title + Node type + Type + Stylesheet + Script + Tab + Tab Title + Tabs + Master Content Type enabled + This Content Type uses + as a Master Content Type. Tabs from Master Content Types are not shown and can only be edited on the Master Content Type itself + No properties defined on this tab. Click on the "add a new property" link at the top to create a new property. + Create matching template + Add icon + + + Sort order + Creation date + Sorting complete. + Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items + + + + Validation + Validation errors must be fixed before the item can be saved + Failed + Saved + Insufficient user permissions, could not complete the operation + Cancelled + Operation was cancelled by a 3rd party add-in + Publishing was cancelled by a 3rd party add-in + Property type already exists + Property type created + DataType: %1%]]> + Propertytype deleted + Document Type saved + Tab created + Tab deleted + Tab with id: %0% deleted + Stylesheet not saved + Stylesheet saved + Stylesheet saved without any errors + Datatype saved + Dictionary item saved + Publishing failed because the parent page isn't published + Content published + and visible on the website + Content saved + Remember to publish to make changes visible + Sent For Approval + Changes have been sent for approval + Media saved + Member group saved + Media saved without any errors + Member saved + Stylesheet Property Saved + Stylesheet saved + Template saved + Error saving user (check log) + User Saved + User type saved + User group saved + Cultures and hostnames saved + Error saving cultures and hostnames + File not saved + file could not be saved. Please check file permissions + File saved + File saved without any errors + Language saved + Media Type saved + Member Type saved + Member Group saved + Template not saved + Please make sure that you do not have 2 templates with the same alias + Template saved + Template saved without any errors! + Content unpublished + Partial view saved + Partial view saved without any errors! + Partial view not saved + An error occurred saving the file. + Permissions saved for + Deleted %0% user groups + %0% was deleted + Enabled %0% users + Disabled %0% users + %0% is now enabled + %0% is now disabled + User groups have been set + Unlocked %0% users + %0% is now unlocked + Member was exported to file + An error occurred while exporting the member + User %0% was deleted + Invite user + Invitation has been re-sent to %0% + Document type was exported to file + An error occurred while exporting the document type + + + Add style + Edit style + Rich text editor styles + Define the styles that should be available in the rich text editor for this stylesheet + Edit stylesheet + Edit stylesheet property + The name displayed in the editor style selector + Preview + How the text will look like in the rich text editor. + Selector + Uses CSS syntax, e.g. "h1" or ".redHeader" + Styles + The CSS that should be applied in the rich text editor, e.g. "color:red;" + Code + Editor + + + Failed to delete template with ID %0% + Edit template + Sections + Insert content area + Insert content area placeholder + Insert + Choose what to insert into your template + Dictionary item + A dictionary item is a placeholder for a translatable piece of text, which makes it easy to create designs for multilingual websites. + Macro + + A Macro is a configurable component which is great for + reusable parts of your design, where you need the option to provide parameters, + such as galleries, forms and lists. + + Value + Displays the value of a named field from the current page, with options to modify the value or fallback to alternative values. + Partial view + + A partial view is a separate template file which can be rendered inside another + template, it's great for reusing markup or for separating complex templates into separate files. + + Master template + No master + Render child template + @RenderBody() placeholder. + ]]> + Define a named section + @section { ... }. This can be rendered in a + specific area of the parent of this template, by using @RenderSection. + ]]> + Render a named section + @RenderSection(name) placeholder. + This renders an area of a child template which is wrapped in a corresponding @section [name]{ ... } definition. + ]]> + Section Name + Section is mandatory + + If mandatory, the child template must contain a @section definition, otherwise an error is shown. + + Query builder + items returned, in + copy to clipboard + I want + all content + content of type "%0%" + from + my website + where + and + is + is not + before + before (including selected date) + after + after (including selected date) + equals + does not equal + contains + does not contain + greater than + greater than or equal to + less than + less than or equal to + Id + Name + Created Date + Last Updated Date + order by + ascending + descending + Template + + + Image + Macro + Choose type of content + Choose a layout + Add a row + Add content + Drop content + Settings applied + This content is not allowed here + This content is allowed here + Click to embed + Click to insert image + Image caption... + Write here... + Grid Layouts + Layouts are the overall work area for the grid editor, usually you only need one or two different layouts + Add Grid Layout + Adjust the layout by setting column widths and adding additional sections + Row configurations + Rows are predefined cells arranged horizontally + Add row configuration + Adjust the row by setting cell widths and adding additional cells + Columns + Total combined number of columns in the grid layout + Settings + Configure what settings editors can change + Styles + Configure what styling editors can change + Allow all editors + Allow all row configurations + Maximum items + Leave blank or set to 0 for unlimited + Set as default + Choose extra + Choose default + are added + Warning + You are deleting the row configuration + + Deleting a row configuration name will result in loss of data for any existing content that is based on this configuration. + + + + Compositions + Group + You have not added any groups + Add group + Inherited from + Add property + Required label + Enable list view + Configures the content item to show a sortable and searchable list of its children, the children will not be shown in the tree + Allowed Templates + Choose which templates editors are allowed to use on content of this type + Allow as root + Allow editors to create content of this type in the root of the content tree. + Allowed child node types + Allow content of the specified types to be created underneath content of this type. + Choose child node + Inherit tabs and properties from an existing document type. New tabs will be added to the current document type or merged if a tab with an identical name exists. + This content type is used in a composition, and therefore cannot be composed itself. + There are no content types available to use as a composition. + Removing a composition will delete all the associated property data. Once you save the document type there's no way back. + Create new + Use existing + Editor settings + Configuration + Yes, delete + was moved underneath + was copied underneath + Select the folder to move + Select the folder to copy + to in the tree structure below + All Document types + All Documents + All media items + using this document type will be deleted permanently, please confirm you want to delete these as well. + using this media type will be deleted permanently, please confirm you want to delete these as well. + using this member type will be deleted permanently, please confirm you want to delete these as well + and all documents using this type + and all media items using this type + and all members using this type + Member can edit + Allow this property value to be edited by the member on their profile page + Is sensitive data + Hide this property value from content editors that don't have access to view sensitive information + Show on member profile + Allow this property value to be displayed on the member profile page + tab has no sort order + Where is this composition used? + This composition is currently used in the composition of the following content types: + 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. + A document type cannot be changed to an Element type once it has been used to create one or more content items. + This is not applicable for an Element type + You have made changes to this property. Are you sure you want to discard them? + + + Add language + Mandatory language + Properties on this language have to be filled out before the node can be published. + Default language + An Umbraco site can only have one default language set. + Switching default language may result in default content missing. + Falls back to + No fall back language + To allow multi-lingual content to fall back to another language if not present in the requested language, select it here. + Fall back language + none + + + + Add parameter + Edit parameter + Enter macro name + Parameters + Define the parameters that should be available when using this macro. + Select partial view macro file + + + Building models + this can take a bit of time, don't worry + Models generated + Models could not be generated + Models generation has failed, see exception in U log + + + Add fallback field + Fallback field + Add default value + Default value + Fallback field + Default value + Casing + Encoding + Choose field + Convert line breaks + Yes, convert line breaks + Replaces line breaks with 'br' html tag + Custom Fields + Date only + Format and encoding + Format as date + Format the value as a date, or a date with time, according to the active culture + HTML encode + Will replace special characters by their HTML equivalent. + Will be inserted after the field value + Will be inserted before the field value + Lowercase + Modify output + None + Output sample + Insert after field + Insert before field + Recursive + Yes, make it recursive + Separator + Standard Fields + Uppercase + URL encode + Will format special characters in URLs + Will only be used when the field values above are empty + This field will only be used if the primary field is empty + Date and time + + + Translation details + Download XML DTD + Fields + Include subpages + + No translator users found. Please create a translator user before you start sending content to translation + The page '%0%' has been send to translation + Send the page '%0%' to translation + Total words + Translate to + Translation completed. + You can preview the pages, you've just translated, by clicking below. If the original page is found, you will get a comparison of the 2 pages. + Translation failed, the XML file might be corrupt + Translation options + Translator + Upload translation XML + + + Content + Content Templates + Media + Cache Browser + Recycle Bin + Created packages + Data Types + Dictionary + Installed packages + Install skin + Install starter kit + Languages + Install local package + Macros + Media Types + Members + Member Groups + Member Roles + Member Types + Document Types + Relation Types + Packages + Packages + Partial Views + Partial View Macro Files + Install from repository + Install Runway + Runway modules + Scripting Files + Scripts + Stylesheets + Templates + Log Viewer + Users + Settings + Templating + Third Party + + + New update ready + %0% is ready, click here for download + No connection to server + Error checking for update. Please review trace-stack for further information + + + Access + Based on the assigned groups and start nodes, the user has access to the following nodes + Assign access + Administrator + Category field + User created + Change Your Password + Change photo + New password + hasn't been locked out + The password hasn't been changed + Confirm new password + You can change your password for accessing the Umbraco Back Office by filling out the form below and click the 'Change Password' button + Content Channel + Create another user + Create new users to give them access to Umbraco. When a new user is created a password will be generated that you can share with the user. + Description field + Disable User + Document Type + Editor + Excerpt field + Failed login attempts + Go to user profile + Add groups to assign access and permissions + Invite another user + Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours. + Language + Set the language you will see in menus and dialogs + Last lockout date + Last login + Password last changed + Username + Media start node + Limit the media library to a specific start node + Media start nodes + Limit the media library to specific start nodes + Sections + Disable Umbraco Access + has not logged in yet + Old password + Password + Reset password + Your password has been changed! + Password changed + Please confirm the new password + Enter your new password + Your new password cannot be blank! + Current password + Invalid current password + There was a difference between the new password and the confirmed password. Please try again! + The confirmed password doesn't match the new password! + Replace child node permissions + You are currently modifying permissions for the pages: + Select pages to modify their permissions + Remove photo + Default permissions + Granular permissions + Set permissions for specific nodes + Profile + Search all children + Add sections to give users access + Select user groups + No start node selected + No start nodes selected + Content start node + Limit the content tree to a specific start node + Content start nodes + Limit the content tree to specific start nodes + User last updated + has been created + The new user has successfully been created. To log in to Umbraco use the password below. + User management + Name + User permissions + User group + has been invited + An invitation has been sent to the new user with details about how to log in to Umbraco. + Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. + Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. + Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. + Writer + Change + Your profile + Your recent history + Session expires in + Invite user + Create user + Send invite + Back to users + Umbraco: Invitation + + + + + + + + + + + +
      + + + + + +
      + +
      + +
      +
      + + + + + + +
      +
      +
      + + + + +
      + + + + +
      +

      + Hi %0%, +

      +

      + You have been invited by %1% to the Umbraco Back Office. +

      +

      + Message from %1%: +
      + %2% +

      + + + + + + +
      + + + + + + +
      + + Click this link to accept the invite + +
      +
      +

      If you cannot click on the link, copy and paste this URL into your browser window:

      + + + + +
      + + %3% + +
      +

      +
      +
      +


      +
      +
      + + ]]>
      + Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? + All + Active + Disabled + Locked out + Invited + Inactive + Name (A-Z) + Name (Z-A) + Newest + Oldest + Last login + No user groups have been added + + + Validation + No validation + Validate as an email address + Validate as a number + Validate as a URL + ...or enter a custom validation + Field is mandatory + Enter a custom validation error message (optional) + Enter a regular expression + Enter a custom validation error message (optional) + You need to add at least + You can only have + Add up to + items + url(s) + url(s) selected + items selected + Invalid date + Not a number + Invalid email + Custom validation + %1% more.]]> + %1% too many.]]> + + + + Value is set to the recommended value: '%0%'. + Value was set to '%1%' for XPath '%2%' in configuration file '%3%'. + Expected value '%1%' for '%2%' in configuration file '%3%', but found '%0%'. + Found unexpected value '%0%' for '%2%' in configuration file '%3%'. + + Custom errors are set to '%0%'. + Custom errors are currently set to '%0%'. It is recommended to set this to '%1%' before go live. + Custom errors successfully set to '%0%'. + MacroErrors are set to '%0%'. + MacroErrors are set to '%0%' which will prevent some or all pages in your site from loading completely if there are any errors in macros. Rectifying this will set the value to '%1%'. + MacroErrors are now set to '%0%'. + + Try Skip IIS Custom Errors is set to '%0%' and you're using IIS version '%1%'. + Try Skip IIS Custom Errors is currently '%0%'. It is recommended to set this to '%1%' for your IIS version (%2%). + Try Skip IIS Custom Errors successfully set to '%0%'. + + File does not exist: '%0%'. + '%0%' in config file '%1%'.]]> + There was an error, check log for full error: %0%. + Database - The database schema is correct for this version of Umbraco + %0% problems were detected with your database schema (Check the log for details) + Some errors were detected while validating the database schema against the current version of Umbraco. + Your website's certificate is valid. + Certificate validation error: '%0%' + Your website's SSL certificate has expired. + Your website's SSL certificate is expiring in %0% days. + Error pinging the URL %0% - '%1%' + You are currently %0% viewing the site using the HTTPS scheme. + The appSetting 'Umbraco.Core.UseHttps' is set to 'false' in your web.config file. Once you access this site using the HTTPS scheme, that should be set to 'true'. + The appSetting 'Umbraco.Core.UseHttps' is set to '%0%' in your web.config file, your cookies are %1% marked as secure. + Could not update the 'Umbraco.Core.UseHttps' setting in your web.config file. Error: %0% + + Enable HTTPS + Sets umbracoSSL setting to true in the appSettings of the web.config file. + The appSetting 'Umbraco.Core.UseHttps' is now set to 'true' in your web.config file, your cookies will be marked as secure. + Fix + Cannot fix a check with a value comparison type of 'ShouldNotEqual'. + Cannot fix a check with a value comparison type of 'ShouldEqual' with a provided value. + Value to fix check not provided. + Debug compilation mode is disabled. + Debug compilation mode is currently enabled. It is recommended to disable this setting before go live. + Debug compilation mode successfully disabled. + Trace mode is disabled. + Trace mode is currently enabled. It is recommended to disable this setting before go live. + Trace mode successfully disabled. + All folders have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + All files have the correct permissions set. + + %0%.]]> + %0%. If they aren't being written to no action need be taken.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was found.]]> + X-Frame-Options used to control whether a site can be IFRAMEd by another was not found.]]> + Set Header in Config + Adds a value to the httpProtocol/customHeaders section of web.config to prevent the site being IFRAMEd by other websites. + A setting to create a header preventing IFRAMEing of the site by other websites has been added to your web.config file. + Could not update web.config file. Error: %0% + X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was found.]]> + X-Content-Type-Options used to protect against MIME sniffing vulnerabilities was not found.]]> + Adds a value to the httpProtocol/customHeaders section of web.config to protect against MIME sniffing vulnerabilities. + A setting to create a header protecting against MIME sniffing vulnerabilities has been added to your web.config file. + Strict-Transport-Security, also known as the HSTS-header, was found.]]> + Strict-Transport-Security was not found.]]> + Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). + The HSTS header has been added to your web.config file. + X-XSS-Protection was found.]]> + X-XSS-Protection was not found.]]> + Adds the header 'X-XSS-Protection' with the value '1; mode=block' to the httpProtocol/customHeaders section of web.config. + The X-XSS-Protection header has been added to your web.config file. + + %0%.]]> + No headers revealing information about the website technology were found. + In the Web.config file, system.net/mailsettings could not be found. + In the Web.config file system.net/mailsettings section, the host is not configured. + SMTP settings are configured correctly and the service is operating as expected. + The SMTP server configured with host '%0%' and port '%1%' could not be reached. Please check to ensure the SMTP settings in the Web.config file system.net/mailsettings are correct. + %0%.]]> + %0%.]]> +

      Results of the scheduled Umbraco Health Checks run on %0% at %1% are as follows:

      %2%]]>
      + Umbraco Health Check Status: %0% + Check All Groups + Check group + + The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button. + You can add your own health checks, have a look at the documentation for more information about custom health checks.

      + ]]> +
      + + + Disable URL tracker + Enable URL tracker + Original URL + Redirected To + Redirect Url Management + The following URLs redirect to this content item: + No redirects have been made + When a published page gets renamed or moved a redirect will automatically be made to the new page. + Are you sure you want to remove the redirect from '%0%' to '%1%'? + Redirect URL removed. + Error removing redirect URL. + This will remove the redirect + Are you sure you want to disable the URL tracker? + URL tracker has now been disabled. + Error disabling the URL tracker, more information can be found in your log file. + URL tracker has now been enabled. + Error enabling the URL tracker, more information can be found in your log file. + + + No Dictionary items to choose from + + + %0% characters left.]]> + %1% too many.]]> + + + Trashed content with Id: {0} related to original parent content with Id: {1} + Trashed media with Id: {0} related to original parent media item with Id: {1} + Cannot automatically restore this item + There is no location where this item can be automatically restored. You can move the item manually using the tree below. + was restored under + + + Direction + Parent to child + Bidirectional + Parent + Child + Count + Relations + Created + Comment + Name + No relations for this relation type. + Relation Type + Relations + + + Getting Started + Redirect URL Management + Content + Welcome + Examine Management + Published Status + Models Builder + Health Check + Profiling + Getting Started + Install Umbraco Forms + + + Go back + Active layout: + Jump to + group + passed + warning + failed + suggestion + Check passed + Check failed + 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 + Create new folder + Partial View + Partial View Macro + Member + Data type + Search the redirect dashboard + Search the user group section + Search the users section + Create item + Create + Edit + Name + Add new row + View more options + Has translation + Missing translation + Dictionary items + + + References + This Data Type has no references. + Used in Document Types + No references to Document Types. + Used in Media Types + No references to Media Types. + Used in Member Types + No references to Member Types. + Used by + Used in Documents + Used in Members + Used in Media + + + Delete Saved Search + Log Levels + Saved Searches + Save Search + Enter a friendly name for your search query + Filter Search + Total Items + Timestamp + Level + Machine + Message + Exception + Properties + Search With Google + Search this message with Google + Search With Bing + Search this message with Bing + Search Our Umbraco + Search this message on Our Umbraco forums and docs + Search Our Umbraco with Google + Search Our Umbraco forums using Google + Search Umbraco Source + Search within Umbraco source code on Github + Search Umbraco Issues + Search Umbraco Issues on Github + Delete this search + Find Logs with Request ID + Find Logs with Namespace + Find Logs with Machine Name + Open + + + Copy %0% + %0% from %1% + Remove all items + Clear clipboard + + Open Property Actions - Close Property Actions - - - Wait - Refresh status - Memory Cache - - - - Reload - Database Cache - - Rebuilding can be expensive. - Use it when reloading is not enough, and you think that the database cache has not been - properly generated—which would indicate some critical Umbraco issue. - ]]> - - Rebuild - Internals - - not need to use it. - ]]> - - Collect - Published Cache Status - Caches - - - Performance profiling - - - Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. -

      -

      - If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. -

      -

      - If you want the profiler to be activated by default for all page renderings, you can use the toggle below. - It will set a cookie in your browser, which then activates the profiler automatically. - In other words, the profiler will only be active by default in your browser - not everyone else's. -

      - ]]> -
      - Activate the profiler by default - Friendly reminder - - - You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. -

      - ]]> -
      - - - Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. -

      -

      - Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. -

      - ]]> -
      - - - Hours of Umbraco training videos are only a click away - - Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

      - ]]> -
      - To get you started - - - Start here - This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section - Find out more - - in the Documentation section of Our Umbraco - ]]> - - - Community Forum - ]]> - - - tutorial videos (some are free, some require a subscription) - ]]> - - - productivity boosting tools and commercial support - ]]> - - - training and certification opportunities - ]]> - - - - Welcome to The Friendly CMS - Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible. - - - 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. - -
      + Close Property Actions + + + Wait + Refresh status + Memory Cache + + + + Reload + Database Cache + + Rebuilding can be expensive. + Use it when reloading is not enough, and you think that the database cache has not been + properly generated—which would indicate some critical Umbraco issue. + ]]> + + Rebuild + Internals + + not need to use it. + ]]> + + Collect + Published Cache Status + Caches + + + Performance profiling + + + Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages. +

      +

      + If you want to activate the profiler for a specific page rendering, simply add umbDebug=true to the querystring when requesting the page. +

      +

      + If you want the profiler to be activated by default for all page renderings, you can use the toggle below. + It will set a cookie in your browser, which then activates the profiler automatically. + In other words, the profiler will only be active by default in your browser - not everyone else's. +

      + ]]> +
      + Activate the profiler by default + Friendly reminder + + + You should never let a production site run in debug mode. Debug mode is turned off by setting debug="false" on the <compilation /> element in web.config. +

      + ]]> +
      + + + Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site. +

      +

      + Debug mode is turned on by setting debug="true" on the <compilation /> element in web.config. +

      + ]]> +
      + + + Hours of Umbraco training videos are only a click away + + Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit umbraco.tv for even more Umbraco videos

      + ]]> +
      + To get you started + + + Start here + This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section + Find out more + + in the Documentation section of Our Umbraco + ]]> + + + Community Forum + ]]> + + + tutorial videos (some are free, some require a subscription) + ]]> + + + productivity boosting tools and commercial support + ]]> + + + training and certification opportunities + ]]> + + + + Welcome to The Friendly CMS + Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible. + + + 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! + + + Create new block + Attach a settings section + Select view + Select stylesheet + Choose thumbnail + Create new + Custom stylesheet + Add stylesheet + Editor apperance + Data models + Catalogue appearance + Background color + Icon color + Content model + Label + Custom view + Settings model + Overlay editor size + Add custom view + Add settings + Overwrite label template + %0%.]]> + Content using this block will be lost. + + Thumbnail + Add thumbnail + Create empty + Clipboard + Settings + Advanced + Force hide content editor + + + 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 94a5c1fe85..5e2a11c24c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -17,6 +17,7 @@ Create group Delete Disable + Edit settings Empty recycle bin Enable Export Document Type @@ -31,6 +32,7 @@ Unpublish Reload Republish entire site + Remove Rename Restore Set permissions for the page %0% @@ -155,6 +157,8 @@ Cancel Confirm More publishing options + Submit + Submit and close Viewing for @@ -296,7 +300,7 @@ Add another text box Remove this text box Content root - Include drafts and unpublished content items. + Include 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! @@ -433,6 +437,9 @@ Close this window Are you sure you want to delete Are you sure you want to disable + Are you sure you want to remove + %0%]]> + %0%]]> Are you sure? Are you sure? Cut @@ -638,6 +645,7 @@ Please place cursor at the left of the two cells you wish to merge You cannot split a cell that hasn't been merged. This property is invalid + Property '%0%' uses editor '%1%' which is not supported in Element Types. Options @@ -2335,6 +2343,7 @@ To manage your website, simply open the Umbraco back office and start adding con Copy %0% %0% from %1% Remove all items + Clear clipboard Open Property Actions @@ -2459,6 +2468,39 @@ 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! + + Create new block + Attach a settings section + Select view + Select stylesheet + Choose thumbnail + Create new + Custom stylesheet + Add stylesheet + Editor apperance + Data models + Catalogue appearance + Background color + Icon color + Content model + Label + Custom view + Settings model + Overlay editor size + Add custom view + Add settings + Overwrite label template + %0%.]]> + Content using this block will be lost. + + Thumbnail + Add thumbnail + Create empty + Clipboard + Settings + Advanced + Force hide content editor + What are Content Templates? Content Templates are pre-defined content that can be selected when creating a new content node. diff --git a/src/Umbraco.Web.UI/Views/Partials/BlockList/Default.cshtml b/src/Umbraco.Web.UI/Views/Partials/BlockList/Default.cshtml new file mode 100644 index 0000000000..19ae842759 --- /dev/null +++ b/src/Umbraco.Web.UI/Views/Partials/BlockList/Default.cshtml @@ -0,0 +1,13 @@ +@inherits UmbracoViewPage +@using Umbraco.Core.Models.Blocks +@{ + if (Model?.Layout == null || !Model.Layout.Any()) { return; } +} +
      + @foreach (var layout in Model.Layout) + { + if (layout?.Udi == null) { continue; } + var data = layout.Data; + @Html.Partial("BlockList/Components/" + data.ContentType.Alias, layout) + } +
      diff --git a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json index d0aa1a1c34..3709e703d9 100644 --- a/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json +++ b/src/Umbraco.Web.UI/config/BackOfficeTours/getting-started.json @@ -188,27 +188,21 @@ "event": "click" }, { - "element": "[data-element~='editor-data-type-picker']", + "element": "[ng-controller*='Umbraco.Editors.DataTypePickerController'] [data-element='editor-data-type-picker']", "elementPreventClick": true, "title": "Editor picker", - "content": "

      In the editor picker dialog we can pick one of the many built-in editors.

      You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors).

      " + "content": "

      In the editor picker dialog we can pick one of the many built-in editors.

      " }, { - "element": "[data-element~='editor-data-type-picker'] [data-element='editor-Textarea']", + "element": "[data-element~='editor-data-type-picker'] [data-element='datatype-Textarea']", "title": "Select editor", "content": "Select the Textarea editor. This will add a textarea to the Welcome Text property.", "event": "click" }, { - "element": "[data-element~='editor-data-type-settings']", - "elementPreventClick": true, + "element": "[data-element='editor-data-type-picker'] [data-element='datatypeconfig-Textarea'] > a", "title": "Editor settings", - "content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed." - }, - { - "element": "[data-element~='editor-data-type-settings'] [data-element='button-submit']", - "title": "Save editor", - "content": "Click Submit to save the changes.", + "content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed.", "event": "click" }, { @@ -317,7 +311,8 @@ "content": "

      To see all our templates click the small triangle to the left of the templates node.

      ", "event": "click", "eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']", - "view": "templatetree" + "view": "templatetree", + "skipStepIfVisible": "#tree [data-element='tree-item-templates'] > div > button[data-element=tree-item-expand].icon-navigation-down" }, { "element": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']", diff --git a/src/Umbraco.Web/BlockListTemplateExtensions.cs b/src/Umbraco.Web/BlockListTemplateExtensions.cs new file mode 100644 index 0000000000..1754eb4fc4 --- /dev/null +++ b/src/Umbraco.Web/BlockListTemplateExtensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web +{ + public static class BlockListTemplateExtensions + { + public const string DefaultFolder = "BlockList/"; + public const string DefaultTemplate = "Default"; + + public static MvcHtmlString GetBlockListHtml(this HtmlHelper html, BlockListModel model, string template = DefaultTemplate) + { + if (model?.Layout == null || !model.Layout.Any()) return new MvcHtmlString(string.Empty); + + var view = DefaultFolder + template; + return html.Partial(view, model); + } + + public static MvcHtmlString GetBlockListHtml(this HtmlHelper html, IPublishedProperty property, string template = DefaultTemplate) => GetBlockListHtml(html, property?.GetValue() as BlockListModel, template); + + public static MvcHtmlString GetBlockListHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias) => GetBlockListHtml(html, contentItem, propertyAlias, DefaultTemplate); + + public static MvcHtmlString GetBlockListHtml(this HtmlHelper html, IPublishedContent contentItem, string propertyAlias, string template) + { + if (propertyAlias == null) throw new ArgumentNullException(nameof(propertyAlias)); + if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyAlias)); + + var prop = contentItem.GetProperty(propertyAlias); + if (prop == null) throw new InvalidOperationException("No property type found with alias " + propertyAlias); + + return GetBlockListHtml(html, prop?.GetValue() as BlockListModel, template); + } + + public static MvcHtmlString GetBlockListHtml(this IPublishedProperty property, HtmlHelper html, string template = DefaultTemplate) => GetBlockListHtml(html, property?.GetValue() as BlockListModel, template); + + public static MvcHtmlString GetBlockListHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias) => GetBlockListHtml(html, contentItem, propertyAlias, DefaultTemplate); + + public static MvcHtmlString GetBlockListHtml(this IPublishedContent contentItem, HtmlHelper html, string propertyAlias, string template) => GetBlockListHtml(html, contentItem, propertyAlias, template); + } +} diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs index 6dfad8c45b..2fa9d80779 100644 --- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs @@ -218,7 +218,7 @@ namespace Umbraco.Web.Compose } catch (Exception e) { - _logger.Error("Failed (will repeat).", e); + _logger.Error(e, "Failed (will repeat)."); } return true; // repeat } diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 05a7927f1b..60d3a6f779 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -322,6 +322,10 @@ namespace Umbraco.Web.Editors "imageUrlGeneratorApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetCropUrl(null, null, null, null, null)) }, + { + "elementTypeApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetAll()) + }, } }, { diff --git a/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs b/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs index eb4d482b14..bd86d74f5d 100644 --- a/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/BlueprintItemBinder.cs @@ -11,8 +11,8 @@ namespace Umbraco.Web.Editors.Binders { } - public BlueprintItemBinder(ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor) - : base(logger, services, umbracoContextAccessor) + public BlueprintItemBinder(ServiceContext services) + : base(services) { } diff --git a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs index a6cba6ce41..378865218c 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs @@ -17,16 +17,13 @@ namespace Umbraco.Web.Editors.Binders /// internal class ContentItemBinder : IModelBinder { - private readonly ContentModelBinderHelper _modelBinderHelper; - - public ContentItemBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor) + public ContentItemBinder() : this(Current.Services) { } - public ContentItemBinder(ILogger logger, ServiceContext services, IUmbracoContextAccessor umbracoContextAccessor) + public ContentItemBinder(ServiceContext services) { Services = services; - _modelBinderHelper = new ContentModelBinderHelper(); } protected ServiceContext Services { get; } @@ -39,10 +36,19 @@ namespace Umbraco.Web.Editors.Binders /// public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { - var model = _modelBinderHelper.BindModelFromMultipartRequest(actionContext, bindingContext); + var model = ContentModelBinderHelper.BindModelFromMultipartRequest(actionContext, bindingContext); if (model == null) return false; - model.PersistedContent = ContentControllerBase.IsCreatingAction(model.Action) ? CreateNew(model) : GetExisting(model); + BindModel(model, ContentControllerBase.IsCreatingAction(model.Action) ? CreateNew(model) : GetExisting(model)); + + return true; + } + + internal static void BindModel(ContentItemSave model, IContent persistedContent) + { + if (model is null) throw new ArgumentNullException(nameof(model)); + + model.PersistedContent = persistedContent; //create the dto from the persisted model if (model.PersistedContent != null) @@ -60,11 +66,9 @@ namespace Umbraco.Web.Editors.Binders }); //now map all of the saved values to the dto - _modelBinderHelper.MapPropertyValuesFromSaved(variant, variant.PropertyCollectionDto); + ContentModelBinderHelper.MapPropertyValuesFromSaved(variant, variant.PropertyCollectionDto); } } - - return true; } protected virtual IContent GetExisting(ContentItemSave model) diff --git a/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs b/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs index b962de74ac..a017ae5afb 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs @@ -15,9 +15,9 @@ namespace Umbraco.Web.Editors.Binders /// /// Helper methods to bind media/member models /// - internal class ContentModelBinderHelper + internal static class ContentModelBinderHelper { - public TModelSave BindModelFromMultipartRequest(HttpActionContext actionContext, ModelBindingContext bindingContext) + public static TModelSave BindModelFromMultipartRequest(HttpActionContext actionContext, ModelBindingContext bindingContext) where TModelSave : IHaveUploadedFiles { var result = actionContext.ReadAsMultipart(SystemDirectories.TempFileUploads); @@ -86,7 +86,7 @@ namespace Umbraco.Web.Editors.Binders /// /// /// - public void MapPropertyValuesFromSaved(IContentProperties saveModel, ContentPropertyCollectionDto dto) + public static void MapPropertyValuesFromSaved(IContentProperties saveModel, ContentPropertyCollectionDto dto) { //NOTE: Don't convert this to linq, this is much quicker foreach (var p in saveModel.Properties) diff --git a/src/Umbraco.Web/Editors/Binders/MediaItemBinder.cs b/src/Umbraco.Web/Editors/Binders/MediaItemBinder.cs index 1084aa16ea..bfd5c853d5 100644 --- a/src/Umbraco.Web/Editors/Binders/MediaItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/MediaItemBinder.cs @@ -13,7 +13,6 @@ namespace Umbraco.Web.Editors.Binders /// internal class MediaItemBinder : IModelBinder { - private readonly ContentModelBinderHelper _modelBinderHelper; private readonly ServiceContext _services; public MediaItemBinder() : this(Current.Services) @@ -23,7 +22,6 @@ namespace Umbraco.Web.Editors.Binders public MediaItemBinder(ServiceContext services) { _services = services; - _modelBinderHelper = new ContentModelBinderHelper(); } /// @@ -34,7 +32,7 @@ namespace Umbraco.Web.Editors.Binders /// public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { - var model = _modelBinderHelper.BindModelFromMultipartRequest(actionContext, bindingContext); + var model = ContentModelBinderHelper.BindModelFromMultipartRequest(actionContext, bindingContext); if (model == null) return false; model.PersistedContent = ContentControllerBase.IsCreatingAction(model.Action) ? CreateNew(model) : GetExisting(model); @@ -44,7 +42,7 @@ namespace Umbraco.Web.Editors.Binders { model.PropertyCollectionDto = Current.Mapper.Map(model.PersistedContent); //now map all of the saved values to the dto - _modelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto); + ContentModelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto); } model.Name = model.Name.Trim(); diff --git a/src/Umbraco.Web/Editors/Binders/MemberBinder.cs b/src/Umbraco.Web/Editors/Binders/MemberBinder.cs index 60b4f85c21..bd51d268ee 100644 --- a/src/Umbraco.Web/Editors/Binders/MemberBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/MemberBinder.cs @@ -20,7 +20,6 @@ namespace Umbraco.Web.Editors.Binders /// internal class MemberBinder : IModelBinder { - private readonly ContentModelBinderHelper _modelBinderHelper; private readonly ServiceContext _services; public MemberBinder() : this(Current.Services) @@ -30,7 +29,6 @@ namespace Umbraco.Web.Editors.Binders public MemberBinder(ServiceContext services) { _services = services; - _modelBinderHelper = new ContentModelBinderHelper(); } /// @@ -41,7 +39,7 @@ namespace Umbraco.Web.Editors.Binders /// public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) { - var model = _modelBinderHelper.BindModelFromMultipartRequest(actionContext, bindingContext); + var model = ContentModelBinderHelper.BindModelFromMultipartRequest(actionContext, bindingContext); if (model == null) return false; model.PersistedContent = ContentControllerBase.IsCreatingAction(model.Action) ? CreateNew(model) : GetExisting(model); @@ -51,7 +49,7 @@ namespace Umbraco.Web.Editors.Binders { model.PropertyCollectionDto = Current.Mapper.Map(model.PersistedContent); //now map all of the saved values to the dto - _modelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto); + ContentModelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto); } model.Name = model.Name.Trim(); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 565eebf80e..b004d49ab0 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -355,6 +355,29 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + return GetEmpty(contentType, parentId); + } + + + /// + /// Gets an empty content item for the document type. + /// + /// + /// + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmptyByKey(Guid contentTypeKey, int parentId) + { + var contentType = Services.ContentTypeService.Get(contentTypeKey); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + return GetEmpty(contentType, parentId); + } + + private ContentItemDisplay GetEmpty(IContentType contentType, int parentId) + { var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0)); var mapped = MapToDisplay(emptyContent); // translate the content type name if applicable @@ -821,7 +844,7 @@ namespace Umbraco.Web.Editors /// /// /// For invariant, the variants collection count will be 1 and this will check if that invariant item has the critical values for persistence (i.e. Name) - /// + /// /// For variant, each variant will be checked for critical data for persistence and if it's not there then it's flags will be reset and it will not /// be persisted. However, we also need to deal with the case where all variants don't pass this check and then there is nothing to save. This also deals /// with removing the Name validation keys based on data annotations validation for items that haven't been marked to be saved. @@ -908,8 +931,8 @@ namespace Umbraco.Web.Editors var savedWithoutErrors = contentItem.Variants .Where(x => x.Save && !variantErrors.Contains((x.Culture, x.Segment))) - .Select(x => (culture: x.Culture, segment: x.Segment)); - + .Select(x => (culture: x.Culture, segment: x.Segment)); + foreach (var (culture, segment) in savedWithoutErrors) { var variantName = GetVariantName(culture, segment); @@ -1258,7 +1281,7 @@ namespace Umbraco.Web.Editors //Now check if there are validation errors on each variant. //If validation errors are detected on a variant and it's state is set to 'publish', then we //need to change it to 'save'. - //It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages. + //It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages. foreach (var variant in contentItem.Variants) { if (variantErrors.Contains((variant.Culture, variant.Segment))) @@ -1389,7 +1412,7 @@ namespace Umbraco.Web.Editors /// Segment to assign the error to /// /// - /// The culture used in the localization message, null by default which means will be used. + /// The culture used in the localization message, null by default which means will be used. /// private void AddVariantValidationError(string culture, string segment, string localizationKey, string cultureToken = null) { @@ -1402,7 +1425,7 @@ namespace Umbraco.Web.Editors } /// - /// Creates the human readable variant name based on culture and segment + /// Creates the human readable variant name based on culture and segment /// /// Culture /// Segment diff --git a/src/Umbraco.Web/Editors/ElementTypeController.cs b/src/Umbraco.Web/Editors/ElementTypeController.cs new file mode 100644 index 0000000000..e49a5eb214 --- /dev/null +++ b/src/Umbraco.Web/Editors/ElementTypeController.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using Umbraco.Core.Services; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + public class ElementTypeController : UmbracoAuthorizedJsonController + { + [HttpGet] + public IEnumerable GetAll() + { + return Services.ContentTypeService + .GetAllElementTypes() + .OrderBy(x => x.SortOrder) + .Select(x => new + { + id = x.Id, + key = x.Key, + name = x.Name, + description = x.Description, + alias = x.Alias, + icon = x.Icon + }); + } + } +} diff --git a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs index 810c2d1bea..9de27248a6 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs @@ -1,15 +1,19 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.PropertyEditors.Validation; namespace Umbraco.Web.Editors.Filters { @@ -125,18 +129,6 @@ namespace Umbraco.Web.Editors.Filters { var properties = modelWithProperties.Properties.ToDictionary(x => x.Alias, x => x); - // Retrieve default messages used for required and regex validatation. We'll replace these - // if set with custom ones if they've been provided for a given property. - var requiredDefaultMessages = new[] - { - _textService.Localize("validation", "invalidNull"), - _textService.Localize("validation", "invalidEmpty") - }; - var formatDefaultMessages = new[] - { - _textService.Localize("validation", "invalidPattern"), - }; - foreach (var p in dto.Properties) { var editor = p.PropertyEditor; @@ -156,7 +148,7 @@ namespace Umbraco.Web.Editors.Filters var postedValue = postedProp.Value; - ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState, requiredDefaultMessages, formatDefaultMessages); + ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState); } @@ -180,26 +172,29 @@ namespace Umbraco.Web.Editors.Filters IDataEditor editor, ContentPropertyDto property, object postedValue, - ModelStateDictionary modelState, - string[] requiredDefaultMessages, - string[] formatDefaultMessages) + ModelStateDictionary modelState) { - var valueEditor = editor.GetValueEditor(property.DataType.Configuration); - foreach (var r in valueEditor.Validate(postedValue, property.IsRequired, property.ValidationRegExp)) + if (property is null) throw new ArgumentNullException(nameof(property)); + if (property.DataType is null) throw new InvalidOperationException($"{nameof(property)}.{nameof(property.DataType)} cannot be null"); + + foreach (var validationResult in PropertyValidationService.ValidatePropertyValue( + _textService, editor, property.DataType, postedValue, property.IsRequired, + property.ValidationRegExp, property.IsRequiredMessage, property.ValidationRegExpMessage)) { - // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). - if (property.IsRequired && !string.IsNullOrWhiteSpace(property.IsRequiredMessage) && requiredDefaultMessages.Contains(r.ErrorMessage, StringComparer.OrdinalIgnoreCase)) - { - r.ErrorMessage = property.IsRequiredMessage; - } - - if (!string.IsNullOrWhiteSpace(property.ValidationRegExp) && !string.IsNullOrWhiteSpace(property.ValidationRegExpMessage) && formatDefaultMessages.Contains(r.ErrorMessage, StringComparer.OrdinalIgnoreCase)) - { - r.ErrorMessage = property.ValidationRegExpMessage; - } - - modelState.AddPropertyError(r, property.Alias, property.Culture, property.Segment); + AddPropertyError(model, modelWithProperties, editor, property, validationResult, modelState); } } + + protected virtual void AddPropertyError( + TModelSave model, + TModelWithProperties modelWithProperties, + IDataEditor editor, + ContentPropertyDto property, + ValidationResult validationResult, + ModelStateDictionary modelState) + { + modelState.AddPropertyError(validationResult, property.Alias, property.Culture, property.Segment); + } + } } diff --git a/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs index 39bd6ab0f4..09995c1104 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs @@ -1,5 +1,8 @@ -using Umbraco.Core.Logging; +using System.ComponentModel.DataAnnotations; +using System.Web.Http.ModelBinding; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -13,5 +16,6 @@ namespace Umbraco.Web.Editors.Filters public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService) : base(logger, umbracoContextAccessor, textService) { } + } } diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 10b1b5dadd..d03472ef5e 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -6,6 +7,7 @@ using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Web.PropertyEditors.Validation; namespace Umbraco.Web { @@ -40,9 +42,9 @@ namespace Umbraco.Web { return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); } - + /// - /// Adds the error to model state correctly for a property so we can use it on the client side. + /// Adds an error to model state for a property so we can use it on the client side. /// /// /// @@ -51,9 +53,24 @@ namespace Umbraco.Web internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string propertyAlias, string culture = "", string segment = "") { - if (culture == null) - culture = ""; - modelState.AddValidationError(result, "_Properties", propertyAlias, + modelState.AddPropertyValidationError(new ContentPropertyValidationResult(result, culture, segment), propertyAlias, culture, segment); + } + + /// + /// Adds the to the model state with the appropriate keys for property errors + /// + /// + /// + /// + /// + /// + internal static void AddPropertyValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, + ValidationResult result, string propertyAlias, string culture = "", string segment = "") + { + 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, // if the segment is null, we'll add the term 'null' as part of the key @@ -166,12 +183,12 @@ namespace Umbraco.Web var delimitedParts = string.Join(".", parts); foreach (var memberName in result.MemberNames) { - modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage); + modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ToString()); withNames = true; } if (!withNames) { - modelState.TryAddModelError($"{delimitedParts}", result.ErrorMessage); + modelState.TryAddModelError($"{delimitedParts}", result.ToString()); } } @@ -236,5 +253,6 @@ namespace Umbraco.Web }; } + } } diff --git a/src/Umbraco.Web/Models/BackOfficeTourStep.cs b/src/Umbraco.Web/Models/BackOfficeTourStep.cs index a64bf15b7f..c21b09523d 100644 --- a/src/Umbraco.Web/Models/BackOfficeTourStep.cs +++ b/src/Umbraco.Web/Models/BackOfficeTourStep.cs @@ -29,5 +29,7 @@ namespace Umbraco.Web.Models public string EventElement { get; set; } [DataMember(Name = "customProperties")] public JObject CustomProperties { get; set; } + [DataMember(Name = "skipStepIfVisible")] + public string SkipStepIfVisible { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs index b6a90a93c3..1ef9207628 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemDisplay.cs @@ -111,6 +111,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "contentTypeId")] public int ContentTypeId { get; set; } + [DataMember(Name = "contentTypeKey")] + public Guid ContentTypeKey { get; set; } + [DataMember(Name = "contentTypeAlias", IsRequired = true)] [Required(AllowEmptyStrings = false)] public string ContentTypeAlias { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs index 4dbbb1385a..aace4645dd 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentItemSave.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "content", Namespace = "")] public class ContentItemSave : IContentSave { - protected ContentItemSave() + public ContentItemSave() { UploadedFiles = new List(); Variants = new List(); diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs index 865057ba24..50ed4684d9 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs @@ -76,6 +76,7 @@ namespace Umbraco.Web.Models.Mapping target.AllowedTemplates = GetAllowedTemplates(source); target.ContentApps = _commonMapper.GetContentApps(source); target.ContentTypeId = source.ContentType.Id; + target.ContentTypeKey = source.ContentType.Key; target.ContentTypeAlias = source.ContentType.Alias; target.ContentTypeName = _localizedTextService.UmbracoDictionaryTranslate(source.ContentType.Name); target.DocumentType = _commonMapper.GetContentType(source, context); diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index cf1bc3c253..2e035430df 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -44,6 +44,9 @@ namespace Umbraco.Web.Models.Mapping property.PropertyType.PropertyEditorAlias); editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label]; + + if (editor == null) + throw new InvalidOperationException($"Could not resolve the property editor {Constants.PropertyEditors.Aliases.Label}"); } dest.Id = property.Id; diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index c30f83170b..64bdac669f 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -178,37 +178,28 @@ namespace Umbraco.Web.Mvc using (RouteTable.Routes.GetReadLock()) { Route surfaceRoute; - if (postedInfo.Area.IsNullOrWhiteSpace()) + + //find the controller in the route table + var surfaceRoutes = RouteTable.Routes.OfType() + .Where(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && + // Only return surface controllers + x.DataTokens["umbraco"].ToString().InvariantEquals("surface") && + // Check for area token if the area is supplied + (postedInfo.Area.IsNullOrWhiteSpace() ? !x.DataTokens.ContainsKey("area") : x.DataTokens["area"].ToString().InvariantEquals(postedInfo.Area))) + .ToList(); + + // If more than one route is found, find one with a matching action + if (surfaceRoutes.Count > 1) { - //find the controller in the route table without an area - var surfaceRoutes = RouteTable.Routes.OfType() - .Where(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && - x.DataTokens.ContainsKey("area") == false).ToList(); - - // If more than one route is found, find one with a matching action - if (surfaceRoutes.Count > 1) - { - surfaceRoute = surfaceRoutes.FirstOrDefault(x => - x.Defaults["action"] != null && - x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); - } - else - { - surfaceRoute = surfaceRoutes.SingleOrDefault(); - } - + surfaceRoute = surfaceRoutes.FirstOrDefault(x => + x.Defaults["action"] != null && + x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); } else { - //find the controller in the route table with the specified area - surfaceRoute = RouteTable.Routes.OfType() - .SingleOrDefault(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && - x.DataTokens.ContainsKey("area") && - x.DataTokens["area"].ToString().InvariantEquals(postedInfo.Area)); + surfaceRoute = surfaceRoutes.FirstOrDefault(); } if (surfaceRoute == null) diff --git a/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs new file mode 100644 index 0000000000..a9dcd6b68b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/BlockEditorPropertyEditor.cs @@ -0,0 +1,390 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web.Razor.Parser.SyntaxTree; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.Models.Editors; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using static Umbraco.Core.Models.Blocks.BlockEditorData; +using static Umbraco.Core.Models.Blocks.BlockItemData; + +namespace Umbraco.Web.PropertyEditors +{ + + /// + /// Abstract class for block editor based editors + /// + public abstract class BlockEditorPropertyEditor : DataEditor + { + public const string ContentTypeKeyPropertyKey = "contentTypeKey"; + public const string UdiPropertyKey = "udi"; + private readonly ILocalizedTextService _localizedTextService; + private readonly Lazy _propertyEditors; + private readonly IDataTypeService _dataTypeService; + private readonly IContentTypeService _contentTypeService; + + public BlockEditorPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService localizedTextService) + : base(logger) + { + _localizedTextService = localizedTextService; + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _contentTypeService = contentTypeService; + } + + // has to be lazy else circular dep in ctor + private PropertyEditorCollection PropertyEditors => _propertyEditors.Value; + + #region Value Editor + + protected override IDataValueEditor CreateValueEditor() => new BlockEditorPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService, _localizedTextService, Logger); + + internal class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference + { + private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; // TODO: Not used yet but we'll need it to fill in the FromEditor/ToEditor + private readonly ILogger _logger; + private readonly BlockEditorValues _blockEditorValues; + + public BlockEditorPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService textService, ILogger logger) + : base(attribute) + { + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _logger = logger; + _blockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, _logger); + Validators.Add(new BlockEditorValidator(_blockEditorValues, propertyEditors, dataTypeService, textService)); + Validators.Add(new MinMaxValidator(_blockEditorValues, textService)); + } + + public IEnumerable GetReferences(object value) + { + var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString(); + + var result = new List(); + var blockEditorData = _blockEditorValues.DeserializeAndClean(rawJson); + if (blockEditorData == null) + return Enumerable.Empty(); + + // loop through all content and settings data + foreach (var row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData)) + { + foreach (var prop in row.PropertyValues) + { + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; + + var valueEditor = propEditor?.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) continue; + + var val = prop.Value.Value?.ToString(); + + var refs = reference.GetReferences(val); + + result.AddRange(refs); + } + } + + return result; + } + + #region Convert database // editor + + // note: there is NO variant support here + + /// + /// Ensure that sub-editor values are translated through their ToEditor methods + /// + /// + /// + /// + /// + /// + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) + { + var val = property.GetValue(culture, segment); + + BlockEditorData blockEditorData; + try + { + blockEditorData = _blockEditorValues.DeserializeAndClean(val); + } + catch (JsonSerializationException) + { + // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format. + return string.Empty; + } + + if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0) + return string.Empty; + + foreach (var row in blockEditorData.BlockValue.ContentData) + { + foreach (var prop in row.PropertyValues) + { + // create a temp property with the value + // - force it to be culture invariant as the block editor can't handle culture variant element properties + prop.Value.PropertyType.Variations = ContentVariation.Nothing; + var tempProp = new Property(prop.Value.PropertyType); + + tempProp.SetValue(prop.Value.Value); + + // convert that temp property, and store the converted value + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; + if (propEditor == null) + { + // NOTE: This logic was borrowed from Nested Content and I'm unsure why it exists. + // if the property editor doesn't exist I think everything will break anyways? + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = tempProp.GetValue()?.ToString(); + continue; + } + + var dataType = dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId); + if (dataType == null) + { + // deal with weird situations by ignoring them (no comment) + row.PropertyValues.Remove(prop.Key); + _logger.Warn( + "ToEditor removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}", + prop.Key, row.Key, property.PropertyType.Alias); + continue; + } + + var tempConfig = dataType.Configuration; + var valEditor = propEditor.GetValueEditor(tempConfig); + var convValue = valEditor.ToEditor(tempProp, dataTypeService); + + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = convValue; + } + } + + // return json convertable object + return blockEditorData.BlockValue; + } + + /// + /// Ensure that sub-editor values are translated through their FromEditor methods + /// + /// + /// + /// + public override object FromEditor(ContentPropertyData editorValue, object currentValue) + { + if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) + return null; + + BlockEditorData blockEditorData; + try + { + blockEditorData = _blockEditorValues.DeserializeAndClean(editorValue.Value); + } + catch (JsonSerializationException) + { + // if this occurs it means the data is invalid, shouldn't happen but has happened if we change the data format. + return string.Empty; + } + + if (blockEditorData == null || blockEditorData.BlockValue.ContentData.Count == 0) + return string.Empty; + + foreach (var row in blockEditorData.BlockValue.ContentData) + { + foreach (var prop in row.PropertyValues) + { + // Fetch the property types prevalue + var propConfiguration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration; + + // Lookup the property editor + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; + if (propEditor == null) continue; + + // Create a fake content property data object + var contentPropData = new ContentPropertyData(prop.Value.Value, propConfiguration); + + // Get the property editor to do it's conversion + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, prop.Value.Value); + + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = newValue; + } + } + + // return json + return JsonConvert.SerializeObject(blockEditorData.BlockValue); + } + + #endregion + } + + /// + /// Validates the min/max of the block editor + /// + private class MinMaxValidator : IValueValidator + { + private readonly BlockEditorValues _blockEditorValues; + private readonly ILocalizedTextService _textService; + + public MinMaxValidator(BlockEditorValues blockEditorValues, ILocalizedTextService textService) + { + _blockEditorValues = blockEditorValues; + _textService = textService; + } + + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + var blockConfig = (BlockListConfiguration)dataTypeConfiguration; + var blockEditorData = _blockEditorValues.DeserializeAndClean(value); + if ((blockEditorData == null && blockConfig?.ValidationLimit?.Min > 0) + || (blockEditorData != null && blockEditorData.Layout.Count() < blockConfig?.ValidationLimit?.Min)) + { + yield return new ValidationResult( + _textService.Localize("validation/entriesShort", new[] { blockConfig.ValidationLimit.Min.ToString(), (blockConfig.ValidationLimit.Min - blockEditorData.Layout.Count()).ToString() }), + new[] { "minCount" }); + } + + if (blockEditorData != null && blockEditorData.Layout.Count() > blockConfig?.ValidationLimit?.Max) + { + yield return new ValidationResult( + _textService.Localize("validation/entriesExceed", new[] { blockConfig.ValidationLimit.Max.ToString(), (blockEditorData.Layout.Count() - blockConfig.ValidationLimit.Max).ToString() }), + new[] { "maxCount" }); + } + } + } + + internal class BlockEditorValidator : ComplexEditorValidator + { + private readonly BlockEditorValues _blockEditorValues; + + public BlockEditorValidator(BlockEditorValues blockEditorValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) : base(propertyEditors, dataTypeService, textService) + { + _blockEditorValues = blockEditorValues; + } + + protected override IEnumerable GetElementTypeValidation(object value) + { + var blockEditorData = _blockEditorValues.DeserializeAndClean(value); + if (blockEditorData != null) + { + foreach (var row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData)) + { + var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Key); + foreach (var prop in row.PropertyValues) + { + elementValidation.AddPropertyTypeValidation( + new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value)); + } + yield return elementValidation; + } + } + } + } + + /// + /// Used to deserialize json values and clean up any values based on the existence of element types and layout structure + /// + internal class BlockEditorValues + { + private readonly Lazy> _contentTypes; + private readonly BlockEditorDataConverter _dataConverter; + private readonly ILogger _logger; + + public BlockEditorValues(BlockEditorDataConverter dataConverter, IContentTypeService contentTypeService, ILogger logger) + { + _contentTypes = new Lazy>(() => contentTypeService.GetAll().ToDictionary(c => c.Key)); + _dataConverter = dataConverter; + _logger = logger; + } + + private IContentType GetElementType(BlockItemData item) + { + _contentTypes.Value.TryGetValue(item.ContentTypeKey, out var contentType); + return contentType; + } + + public BlockEditorData DeserializeAndClean(object propertyValue) + { + if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) + return null; + + var blockEditorData = _dataConverter.Deserialize(propertyValue.ToString()); + + if (blockEditorData.BlockValue.ContentData.Count == 0) + { + // if there's no content ensure there's no settings too + blockEditorData.BlockValue.SettingsData.Clear(); + return null; + } + + var contentTypePropertyTypes = new Dictionary>(); + + // filter out any content that isn't referenced in the layout references + foreach(var block in blockEditorData.BlockValue.ContentData.Where(x => blockEditorData.References.Any(r => r.ContentUdi == x.Udi))) + { + ResolveBlockItemData(block, contentTypePropertyTypes); + } + // filter out any settings that isn't referenced in the layout references + foreach (var block in blockEditorData.BlockValue.SettingsData.Where(x => blockEditorData.References.Any(r => r.SettingsUdi == x.Udi))) + { + ResolveBlockItemData(block, contentTypePropertyTypes); + } + + // remove blocks that couldn't be resolved + blockEditorData.BlockValue.ContentData.RemoveAll(x => x.ContentTypeAlias.IsNullOrWhiteSpace()); + blockEditorData.BlockValue.SettingsData.RemoveAll(x => x.ContentTypeAlias.IsNullOrWhiteSpace()); + + return blockEditorData; + } + + private bool ResolveBlockItemData(BlockItemData block, Dictionary> contentTypePropertyTypes) + { + var contentType = GetElementType(block); + if (contentType == null) + return false; + + // get the prop types for this content type but keep a dictionary of found ones so we don't have to keep re-looking and re-creating + // objects on each iteration. + if (!contentTypePropertyTypes.TryGetValue(contentType.Alias, out var propertyTypes)) + propertyTypes = contentTypePropertyTypes[contentType.Alias] = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x); + + var propValues = new Dictionary(); + + // find any keys that are not real property types and remove them + foreach (var prop in block.RawPropertyValues.ToList()) + { + // doesn't exist so remove it + if (!propertyTypes.TryGetValue(prop.Key, out var propType)) + { + block.RawPropertyValues.Remove(prop.Key); + _logger.Warn("The property {PropertyKey} for block {BlockKey} was removed because the property type {PropertyTypeAlias} was not found on {ContentTypeAlias}", + prop.Key, block.Key, prop.Key, contentType.Alias); + } + else + { + // set the value to include the resolved property type + propValues[prop.Key] = new BlockPropertyValue + { + PropertyType = propType, + Value = prop.Value + }; + } + } + + block.ContentTypeAlias = contentType.Alias; + block.PropertyValues = propValues; + + return true; + } + } + + #endregion + + } +} diff --git a/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs new file mode 100644 index 0000000000..bfbbc80179 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/BlockListConfiguration.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json; +using System; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + + /// + /// The configuration object for the Block List editor + /// + public class BlockListConfiguration + { + + + [ConfigurationField("blocks", "Available Blocks", "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html", Description = "Define the available blocks.")] + public BlockConfiguration[] Blocks { get; set; } + + public class BlockConfiguration + { + + [JsonProperty("backgroundColor")] + public string BackgroundColor { get; set; } + + [JsonProperty("iconColor")] + public string IconColor { get; set; } + + [JsonProperty("thumbnail")] + public string Thumbnail { get; set; } + + // TODO: This is named inconsistently in JS but renaming it needs to be done in quite a lot of places, this should be contentElementTypeKey + [JsonProperty("contentTypeKey")] + public Guid ContentElementTypeKey { get; set; } + + [JsonProperty("settingsElementTypeKey")] + public Guid? SettingsElementTypeKey { get; set; } + + [JsonProperty("view")] + public string View { get; set; } + + [JsonProperty("stylesheet")] + public string Stylesheet { get; set; } + + [JsonProperty("label")] + public string Label { get; set; } + + [JsonProperty("editorSize")] + public string EditorSize { get; set; } + + [JsonProperty("forceHideContentEditorInOverlay")] + public bool ForceHideContentEditorInOverlay { get; set; } + } + + [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of blocks")] + public NumberRange ValidationLimit { get; set; } = new NumberRange(); + + public class NumberRange + { + [JsonProperty("min")] + public int? Min { get; set; } + + [JsonProperty("max")] + public int? Max { get; set; } + } + + [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", Description = "Live editing in editor overlays for live updated custom views.")] + public bool UseLiveEditing { get; set; } + + [ConfigurationField("useInlineEditingAsDefault", "Inline editing mode", "boolean", Description = "Use the inline editor as the default block view.")] + public bool UseInlineEditingAsDefault { get; set; } + + [ConfigurationField("maxPropertyWidth", "Property editor width", "textstring", Description = "optional css overwrite, example: 800px or 100%")] + public string MaxPropertyWidth { get; set; } + + + } +} diff --git a/src/Umbraco.Web/PropertyEditors/BlockListConfigurationEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockListConfigurationEditor.cs new file mode 100644 index 0000000000..3a4e3eae9b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/BlockListConfigurationEditor.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + internal class BlockListConfigurationEditor : ConfigurationEditor + { + public BlockListConfigurationEditor() + { + + } + + } +} diff --git a/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs new file mode 100644 index 0000000000..42023382f1 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/BlockListPropertyEditor.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Represents a block list property editor. + /// + [DataEditor( + Constants.PropertyEditors.Aliases.BlockList, + "Block List", + "blocklist", + ValueType = ValueTypes.Json, + Group = Constants.PropertyEditors.Groups.Lists, + Icon = "icon-thumbnail-list")] + public class BlockListPropertyEditor : BlockEditorPropertyEditor + { + public BlockListPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService localizedTextService) + : base(logger, propertyEditors, dataTypeService, contentTypeService, localizedTextService) + { } + + #region Pre Value Editor + + protected override IConfigurationEditor CreateConfigurationEditor() => new BlockListConfigurationEditor(); + + #endregion + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs b/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs new file mode 100644 index 0000000000..365a3fff6b --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ComplexEditorValidator.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.PropertyEditors.Validation; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// Used to validate complex editors that contain nested editors + /// + public abstract class ComplexEditorValidator : IValueValidator + { + private readonly PropertyValidationService _propertyValidationService; + + public ComplexEditorValidator(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) + { + _propertyValidationService = new PropertyValidationService(propertyEditors, dataTypeService, textService); + } + + /// + /// Return a single for all sub nested validation results in the complex editor + /// + /// + /// + /// + /// + public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) + { + var elementTypeValues = GetElementTypeValidation(value).ToList(); + var rowResults = GetNestedValidationResults(elementTypeValues).ToList(); + + if (rowResults.Count > 0) + { + var result = new ComplexEditorValidationResult(); + foreach(var rowResult in rowResults) + { + result.ValidationResults.Add(rowResult); + } + return result.Yield(); + } + + return Enumerable.Empty(); + } + + + protected abstract IEnumerable GetElementTypeValidation(object value); + + /// + /// Return a nested validation result per row (Element Type) + /// + /// + /// + protected IEnumerable GetNestedValidationResults(IEnumerable elements) + { + foreach (var row in elements) + { + var elementTypeValidationResult = new ComplexEditorElementTypeValidationResult(row.ElementTypeAlias, row.Id); + + foreach (var prop in row.PropertyTypeValidation) + { + var propValidationResult = new ComplexEditorPropertyTypeValidationResult(prop.PropertyType.Alias); + + foreach (var validationResult in _propertyValidationService.ValidatePropertyValue(prop.PropertyType, prop.PostedValue)) + { + // add the result to the property results + propValidationResult.AddValidationResult(validationResult); + } + + // add the property results to the element type results + if (propValidationResult.ValidationResults.Count > 0) + elementTypeValidationResult.ValidationResults.Add(propValidationResult); + } + + if (elementTypeValidationResult.ValidationResults.Count > 0) + { + yield return elementTypeValidationResult; + } + } + } + + public class PropertyTypeValidationModel + { + public PropertyTypeValidationModel(PropertyType propertyType, object postedValue) + { + PostedValue = postedValue; + PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); + } + + public object PostedValue { get; } + public PropertyType PropertyType { get; } + } + + public class ElementTypeValidationModel + { + public ElementTypeValidationModel(string elementTypeAlias, Guid id) + { + ElementTypeAlias = elementTypeAlias; + Id = id; + } + + private List _list = new List(); + + public IEnumerable PropertyTypeValidation => _list; + + public string ElementTypeAlias { get; } + public Guid Id { get; } + + public void AddPropertyTypeValidation(PropertyTypeValidationModel propValidation) => _list.Add(propValidation); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs index 0f53207462..89190883c8 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs @@ -3,6 +3,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { + /// /// Represents the configuration for the nested content value editor. /// diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 564630c574..97a2948626 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Umbraco.Core; @@ -13,16 +14,18 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; +using Umbraco.Web.PropertyEditors.Validation; namespace Umbraco.Web.PropertyEditors { + /// /// Represents a nested content property editor. /// [DataEditor( Constants.PropertyEditors.Aliases.NestedContent, "Nested Content", - "nestedcontent", + "nestedcontent", ValueType = ValueTypes.Json, Group = Constants.PropertyEditors.Groups.Lists, Icon = "icon-thumbnail-list")] @@ -31,14 +34,20 @@ namespace Umbraco.Web.PropertyEditors private readonly Lazy _propertyEditors; private readonly IDataTypeService _dataTypeService; private readonly IContentTypeService _contentTypeService; + private readonly ILocalizedTextService _localizedTextService; internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias"; + [Obsolete("Use the constructor specifying all parameters instead")] public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) + : this(logger, propertyEditors, dataTypeService, contentTypeService, Current.Services.TextService) { } + + public NestedContentPropertyEditor(ILogger logger, Lazy propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService localizedTextService) : base (logger) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; _contentTypeService = contentTypeService; + _localizedTextService = localizedTextService; } // has to be lazy else circular dep in ctor @@ -52,21 +61,23 @@ namespace Umbraco.Web.PropertyEditors #region Value Editor - protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService); + protected override IDataValueEditor CreateValueEditor() => new NestedContentPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService, _localizedTextService, Logger); internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; + private readonly ILogger _logger; private readonly NestedContentValues _nestedContentValues; - public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService) + public NestedContentPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService textService, ILogger logger) : base(attribute) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; + _logger = logger; _nestedContentValues = new NestedContentValues(contentTypeService); - Validators.Add(new NestedContentValidator(propertyEditors, dataTypeService, _nestedContentValues)); + Validators.Add(new NestedContentValidator(_nestedContentValues, propertyEditors, dataTypeService, textService)); } /// @@ -89,143 +100,153 @@ namespace Umbraco.Web.PropertyEditors public override string ConvertDbToString(PropertyType propertyType, object propertyValue, IDataTypeService dataTypeService) { - var vals = _nestedContentValues.GetPropertyValues(propertyValue, out var deserialized).ToList(); + var rows = _nestedContentValues.GetPropertyValues(propertyValue); - if (vals.Count == 0) + if (rows.Count == 0) return string.Empty; - foreach (var row in vals) + foreach (var row in rows.ToList()) { - if (row.PropType == null) - { - // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(row.PropKey) == false) - row.JsonRowValue[row.PropKey] = null; - } - else + foreach(var prop in row.PropertyValues.ToList()) { try { // convert the value, and store the converted value - var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; if (propEditor == null) continue; - var tempConfig = dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var tempConfig = dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration; var valEditor = propEditor.GetValueEditor(tempConfig); - var convValue = valEditor.ConvertDbToString(row.PropType, row.JsonRowValue[row.PropKey]?.ToString(), dataTypeService); - row.JsonRowValue[row.PropKey] = convValue; + var convValue = valEditor.ConvertDbToString(prop.Value.PropertyType, prop.Value.Value, dataTypeService); + + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = convValue; } - catch (InvalidOperationException) + catch (InvalidOperationException ex) { // deal with weird situations by ignoring them (no comment) - row.JsonRowValue[row.PropKey] = null; + row.RawPropertyValues.Remove(prop.Key); + _logger.Warn( + ex, + "ConvertDbToString removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}", + prop.Key, row.Id, propertyType.Alias); } } } - return JsonConvert.SerializeObject(deserialized).ToXmlString(); + return JsonConvert.SerializeObject(rows).ToXmlString(); } #endregion - + #region Convert database // editor // note: there is NO variant support here + /// + /// Ensure that sub-editor values are translated through their ToEditor methods + /// + /// + /// + /// + /// + /// public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var val = property.GetValue(culture, segment); - var vals = _nestedContentValues.GetPropertyValues(val, out var deserialized).ToList(); + var rows = _nestedContentValues.GetPropertyValues(val); - if (vals.Count == 0) + if (rows.Count == 0) return string.Empty; - foreach (var row in vals) + foreach (var row in rows.ToList()) { - if (row.PropType == null) - { - // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(row.PropKey) == false) - row.JsonRowValue[row.PropKey] = null; - } - else + foreach(var prop in row.PropertyValues.ToList()) { try { // create a temp property with the value // - force it to be culture invariant as NC can't handle culture variant element properties - row.PropType.Variations = ContentVariation.Nothing; - var tempProp = new Property(row.PropType); - tempProp.SetValue(row.JsonRowValue[row.PropKey] == null ? null : row.JsonRowValue[row.PropKey].ToString()); + prop.Value.PropertyType.Variations = ContentVariation.Nothing; + var tempProp = new Property(prop.Value.PropertyType); + + tempProp.SetValue(prop.Value.Value); // convert that temp property, and store the converted value - var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; if (propEditor == null) { - row.JsonRowValue[row.PropKey] = tempProp.GetValue()?.ToString(); + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = tempProp.GetValue()?.ToString(); continue; } - var tempConfig = dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var tempConfig = dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration; var valEditor = propEditor.GetValueEditor(tempConfig); var convValue = valEditor.ToEditor(tempProp, dataTypeService); - row.JsonRowValue[row.PropKey] = convValue == null ? null : JToken.FromObject(convValue); + + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = convValue == null ? null : JToken.FromObject(convValue); } - catch (InvalidOperationException) + catch (InvalidOperationException ex) { // deal with weird situations by ignoring them (no comment) - row.JsonRowValue[row.PropKey] = null; + row.RawPropertyValues.Remove(prop.Key); + _logger.Warn( + ex, + "ToEditor removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}", + prop.Key, row.Id, property.PropertyType.Alias); } } } - // return json - return deserialized; + // return the object, there's a native json converter for this so it will serialize correctly + return rows; } + /// + /// Ensure that sub-editor values are translated through their FromEditor methods + /// + /// + /// + /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString())) return null; - var vals = _nestedContentValues.GetPropertyValues(editorValue.Value, out var deserialized).ToList(); + var rows = _nestedContentValues.GetPropertyValues(editorValue.Value); - if (vals.Count == 0) + if (rows.Count == 0) return string.Empty; - foreach (var row in vals) + foreach (var row in rows.ToList()) { - if (row.PropType == null) - { - // type not found, and property is not system: just delete the value - if (IsSystemPropertyKey(row.PropKey) == false) - row.JsonRowValue[row.PropKey] = null; - } - else + foreach(var prop in row.PropertyValues.ToList()) { // Fetch the property types prevalue - var propConfiguration = _dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; + var propConfiguration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration; // Lookup the property editor - var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; if (propEditor == null) continue; // Create a fake content property data object - var contentPropData = new ContentPropertyData(row.JsonRowValue[row.PropKey], propConfiguration); + var contentPropData = new ContentPropertyData(prop.Value.Value, propConfiguration); // Get the property editor to do it's conversion - var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, row.JsonRowValue[row.PropKey]); + var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, prop.Value.Value); - // Store the value back - row.JsonRowValue[row.PropKey] = (newValue == null) ? null : JToken.FromObject(newValue); + // update the raw value since this is what will get serialized out + row.RawPropertyValues[prop.Key] = (newValue == null) ? null : JToken.FromObject(newValue); } } // return json - return JsonConvert.SerializeObject(deserialized); + return JsonConvert.SerializeObject(rows); } #endregion @@ -235,98 +256,58 @@ namespace Umbraco.Web.PropertyEditors var result = new List(); - foreach (var row in _nestedContentValues.GetPropertyValues(rawJson, out _)) + foreach (var row in _nestedContentValues.GetPropertyValues(rawJson)) { - if (row.PropType == null) continue; + foreach(var prop in row.PropertyValues) + { + var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias]; - var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; + var valueEditor = propEditor?.GetValueEditor(); + if (!(valueEditor is IDataValueReference reference)) continue; - var valueEditor = propEditor?.GetValueEditor(); - if (!(valueEditor is IDataValueReference reference)) continue; + var val = prop.Value.Value?.ToString(); - var val = row.JsonRowValue[row.PropKey]?.ToString(); + var refs = reference.GetReferences(val); - var refs = reference.GetReferences(val); - - result.AddRange(refs); + result.AddRange(refs); + } } return result; } } - internal class NestedContentValidator : IValueValidator + /// + /// Validator for nested content to ensure that all nesting of editors is validated + /// + internal class NestedContentValidator : ComplexEditorValidator { - private readonly PropertyEditorCollection _propertyEditors; - private readonly IDataTypeService _dataTypeService; private readonly NestedContentValues _nestedContentValues; - public NestedContentValidator(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, NestedContentValues nestedContentValues) + public NestedContentValidator(NestedContentValues nestedContentValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) + : base(propertyEditors, dataTypeService, textService) { - _propertyEditors = propertyEditors; - _dataTypeService = dataTypeService; _nestedContentValues = nestedContentValues; } - public IEnumerable Validate(object rawValue, string valueType, object dataTypeConfiguration) + protected override IEnumerable GetElementTypeValidation(object value) { - var validationResults = new List(); - - foreach(var row in _nestedContentValues.GetPropertyValues(rawValue, out _)) + foreach (var row in _nestedContentValues.GetPropertyValues(value)) { - if (row.PropType == null) continue; - - var config = _dataTypeService.GetDataType(row.PropType.DataTypeId).Configuration; - var propertyEditor = _propertyEditors[row.PropType.PropertyEditorAlias]; - if (propertyEditor == null) continue; - - foreach (var validator in propertyEditor.GetValueEditor().Validators) + var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Id); + foreach (var prop in row.PropertyValues) { - foreach (var result in validator.Validate(row.JsonRowValue[row.PropKey], propertyEditor.GetValueEditor().ValueType, config)) - { - result.ErrorMessage = "Item " + (row.RowIndex + 1) + " '" + row.PropType.Name + "' " + result.ErrorMessage; - validationResults.Add(result); - } - } - - // Check mandatory - if (row.PropType.Mandatory) - { - if (row.JsonRowValue[row.PropKey] == null) - { - var message = string.IsNullOrWhiteSpace(row.PropType.MandatoryMessage) - ? $"'{row.PropType.Name}' cannot be null" - : row.PropType.MandatoryMessage; - validationResults.Add(new ValidationResult($"Item {(row.RowIndex + 1)}: {message}", new[] { row.PropKey })); - } - else if (row.JsonRowValue[row.PropKey].ToString().IsNullOrWhiteSpace() || (row.JsonRowValue[row.PropKey].Type == JTokenType.Array && !row.JsonRowValue[row.PropKey].HasValues)) - { - var message = string.IsNullOrWhiteSpace(row.PropType.MandatoryMessage) - ? $"'{row.PropType.Name}' cannot be empty" - : row.PropType.MandatoryMessage; - validationResults.Add(new ValidationResult($"Item {(row.RowIndex + 1)}: {message}", new[] { row.PropKey })); - } - } - - // Check regex - if (!row.PropType.ValidationRegExp.IsNullOrWhiteSpace() - && row.JsonRowValue[row.PropKey] != null && !row.JsonRowValue[row.PropKey].ToString().IsNullOrWhiteSpace()) - { - var regex = new Regex(row.PropType.ValidationRegExp); - if (!regex.IsMatch(row.JsonRowValue[row.PropKey].ToString())) - { - var message = string.IsNullOrWhiteSpace(row.PropType.ValidationRegExpMessage) - ? $"'{row.PropType.Name}' is invalid, it does not match the correct pattern" - : row.PropType.ValidationRegExpMessage; - validationResults.Add(new ValidationResult($"Item {(row.RowIndex + 1)}: {message}", new[] { row.PropKey })); - } + elementValidation.AddPropertyTypeValidation( + new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value)); } + yield return elementValidation; } - - return validationResults; } } + /// + /// Used to deserialize the nested content serialized value + /// internal class NestedContentValues { private readonly Lazy> _contentTypes; @@ -336,84 +317,110 @@ namespace Umbraco.Web.PropertyEditors _contentTypes = new Lazy>(() => contentTypeService.GetAll().ToDictionary(c => c.Alias)); } - private IContentType GetElementType(JObject item) + private IContentType GetElementType(NestedContentRowValue item) { - var contentTypeAlias = item[ContentTypeAliasPropertyKey]?.ToObject() ?? string.Empty; - _contentTypes.Value.TryGetValue(contentTypeAlias, out var contentType); + _contentTypes.Value.TryGetValue(item.ContentTypeAlias, out var contentType); return contentType; } - public IEnumerable GetPropertyValues(object propertyValue, out List deserialized) + /// + /// Deserialize the raw json property value + /// + /// + /// + public IReadOnlyList GetPropertyValues(object propertyValue) { - var rowValues = new List(); - - deserialized = null; - if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString())) - return Enumerable.Empty(); + return new List(); - deserialized = JsonConvert.DeserializeObject>(propertyValue.ToString()); + var rowValues = JsonConvert.DeserializeObject>(propertyValue.ToString()); // There was a note here about checking if the result had zero items and if so it would return null, so we'll continue to do that // The original note was: "Issue #38 - Keep recursive property lookups working" // Which is from the original NC tracker: https://github.com/umco/umbraco-nested-content/issues/38 // This check should be used everywhere when iterating NC prop values, instead of just the one previous place so that // empty values don't get persisted when there is nothing, it should actually be null. - if (deserialized == null || deserialized.Count == 0) - return Enumerable.Empty(); + if (rowValues == null || rowValues.Count == 0) + return new List(); - var index = 0; + var contentTypePropertyTypes = new Dictionary>(); - foreach (var o in deserialized) + foreach (var row in rowValues) { - var propValues = o; - - var contentType = GetElementType(propValues); + var contentType = GetElementType(row); if (contentType == null) continue; - var propertyTypes = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x); - var propAliases = propValues.Properties().Select(x => x.Name); - foreach (var propAlias in propAliases) + // get the prop types for this content type but keep a dictionary of found ones so we don't have to keep re-looking and re-creating + // objects on each iteration. + if (!contentTypePropertyTypes.TryGetValue(contentType.Alias, out var propertyTypes)) + propertyTypes = contentTypePropertyTypes[contentType.Alias] = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x); + + // find any keys that are not real property types and remove them + foreach(var prop in row.RawPropertyValues.ToList()) { - propertyTypes.TryGetValue(propAlias, out var propType); - rowValues.Add(new RowValue(propAlias, propType, propValues, index)); + if (IsSystemPropertyKey(prop.Key)) continue; + + // doesn't exist so remove it + if (!propertyTypes.TryGetValue(prop.Key, out var propType)) + { + row.RawPropertyValues.Remove(prop.Key); + } + else + { + // set the value to include the resolved property type + row.PropertyValues[prop.Key] = new NestedContentPropertyValue + { + PropertyType = propType, + Value = prop.Value + }; + } } - index++; } return rowValues; } - internal class RowValue + /// + /// Used during deserialization to populate the property value/property type of a nested content row property + /// + internal class NestedContentPropertyValue { - public RowValue(string propKey, PropertyType propType, JObject propValues, int index) - { - PropKey = propKey ?? throw new ArgumentNullException(nameof(propKey)); - PropType = propType; - JsonRowValue = propValues ?? throw new ArgumentNullException(nameof(propValues)); - RowIndex = index; - } + public object Value { get; set; } + public PropertyType PropertyType { get; set; } + } + + /// + /// Used to deserialize a nested content row + /// + internal class NestedContentRowValue + { + [JsonProperty("key")] + public Guid Id{ get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("ncContentTypeAlias")] + public string ContentTypeAlias { get; set; } /// - /// The current property key being iterated for the row value + /// The remaining properties will be serialized to a dictionary /// - public string PropKey { get; } + /// + /// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket + /// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm + /// NestedContent serializes to string, int, whatever eg + /// "stringValue":"Some String","numericValue":125,"otherNumeric":null + /// + [JsonExtensionData] + public IDictionary RawPropertyValues { get; set; } /// - /// The of the value (if any), this may be null + /// Used during deserialization to convert the raw property data into data with a property type context /// - public PropertyType PropType { get; } - - /// - /// The json values for the current row - /// - public JObject JsonRowValue { get; } - - /// - /// The Nested Content row index - /// - public int RowIndex { get; } + [JsonIgnore] + public IDictionary PropertyValues { get; set; } = new Dictionary(); } } diff --git a/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs b/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs new file mode 100644 index 0000000000..9c68deb7b5 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Web.PropertyEditors.Validation +{ + /// + /// A collection of for an element type within complex editor represented by an Element Type + /// + /// + /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + /// https://github.com/umbraco/Umbraco-CMS/pull/8339 + /// + public class ComplexEditorElementTypeValidationResult : ValidationResult + { + public ComplexEditorElementTypeValidationResult(string elementTypeAlias, Guid blockId) + : base(string.Empty) + { + ElementTypeAlias = elementTypeAlias; + BlockId = blockId; + } + + public IList ValidationResults { get; } = new List(); + + /// + /// The element type alias of the validation result + /// + /// + /// This is useful for debugging purposes but it's not actively used in the angular app + /// + public string ElementTypeAlias { get; } + + /// + /// The Block ID of the validation result + /// + /// + /// This is the GUID id of the content item based on the element type + /// + public Guid BlockId { get; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs b/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs new file mode 100644 index 0000000000..3036529fb7 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Umbraco.Web.PropertyEditors.Validation +{ + /// + /// A collection of for a property type within a complex editor represented by an Element Type + /// + /// + /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + /// https://github.com/umbraco/Umbraco-CMS/pull/8339 + /// + public class ComplexEditorPropertyTypeValidationResult : ValidationResult + { + public ComplexEditorPropertyTypeValidationResult(string propertyTypeAlias) + : base(string.Empty) + { + PropertyTypeAlias = propertyTypeAlias; + } + + private readonly List _validationResults = new List(); + + public void AddValidationResult(ValidationResult validationResult) + { + if (validationResult is ComplexEditorValidationResult && _validationResults.Any(x => x is ComplexEditorValidationResult)) + throw new InvalidOperationException($"Cannot add more than one {typeof(ComplexEditorValidationResult)}"); + + _validationResults.Add(validationResult); + } + + public IReadOnlyList ValidationResults => _validationResults; + public string PropertyTypeAlias { get; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorValidationResult.cs b/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorValidationResult.cs new file mode 100644 index 0000000000..178e1ec1f4 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/Validation/ComplexEditorValidationResult.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Web.PropertyEditors.Validation +{ + + /// + /// A collection of for a complex editor represented by an Element Type + /// + /// + /// For example, each represents validation results for a row in Nested Content. + /// + /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + /// https://github.com/umbraco/Umbraco-CMS/pull/8339 + /// + public class ComplexEditorValidationResult : ValidationResult + { + public ComplexEditorValidationResult() + : base(string.Empty) + { + } + + public IList ValidationResults { get; } = new List(); + } +} diff --git a/src/Umbraco.Web/PropertyEditors/Validation/ContentPropertyValidationResult.cs b/src/Umbraco.Web/PropertyEditors/Validation/ContentPropertyValidationResult.cs new file mode 100644 index 0000000000..0f161c5628 --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/Validation/ContentPropertyValidationResult.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json; +using System.ComponentModel.DataAnnotations; + +namespace Umbraco.Web.PropertyEditors.Validation +{ + /// + /// Custom for content properties + /// + /// + /// This clones the original result and then ensures the nested result if it's the correct type. + /// + /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + /// https://github.com/umbraco/Umbraco-CMS/pull/8339 + /// + public class ContentPropertyValidationResult : ValidationResult + { + private readonly string _culture; + private readonly string _segment; + + public ContentPropertyValidationResult(ValidationResult nested, string culture, string segment) + : base(nested.ErrorMessage, nested.MemberNames) + { + ComplexEditorResults = nested as ComplexEditorValidationResult; + _culture = culture; + _segment = segment; + } + + /// + /// Nested validation results for the content property + /// + /// + /// There can be nested results for complex editors that contain other editors + /// + public ComplexEditorValidationResult ComplexEditorResults { get; } + + /// + /// Return the if is null, else the serialized + /// complex validation results + /// + /// + public override string ToString() + { + if (ComplexEditorResults == null) + return base.ToString(); + + var json = JsonConvert.SerializeObject(this, new ValidationResultConverter(_culture, _segment)); + return json; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs b/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs new file mode 100644 index 0000000000..2fb000e2ca --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/Validation/ValidationResultConverter.cs @@ -0,0 +1,155 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web.Http.ModelBinding; +using Umbraco.Core; + +namespace Umbraco.Web.PropertyEditors.Validation +{ + /// + /// Custom json converter for and + /// + /// + /// This converter is specifically used to convert validation results for content in order to be able to have nested + /// validation results for complex editors. + /// + /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: + /// https://github.com/umbraco/Umbraco-CMS/pull/8339 + /// + internal class ValidationResultConverter : JsonConverter + { + private readonly string _culture; + private readonly string _segment; + + /// + /// Constructor + /// + /// The culture of the containing property which will be transfered to all child model state + /// The segment of the containing property which will be transfered to all child model state + public ValidationResultConverter(string culture = "", string segment = "") + { + _culture = culture; + _segment = segment; + } + + public override bool CanConvert(Type objectType) => typeof(ValidationResult).IsAssignableFrom(objectType); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var camelCaseSerializer = new JsonSerializer + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + DefaultValueHandling = DefaultValueHandling.Ignore + }; + foreach (var c in serializer.Converters) + camelCaseSerializer.Converters.Add(c); + + var validationResult = (ValidationResult)value; + + if (validationResult is ComplexEditorValidationResult nestedResult && nestedResult.ValidationResults.Count > 0) + { + var ja = new JArray(); + foreach(var nested in nestedResult.ValidationResults) + { + // recurse to write out the ComplexEditorElementTypeValidationResult + var block = JObject.FromObject(nested, camelCaseSerializer); + ja.Add(block); + } + if (nestedResult.ValidationResults.Count > 0) + { + ja.WriteTo(writer); + } + } + else if (validationResult is ComplexEditorElementTypeValidationResult elementTypeValidationResult && elementTypeValidationResult.ValidationResults.Count > 0) + { + var joElementType = new JObject + { + { "$id", elementTypeValidationResult.BlockId }, + + // We don't use this anywhere, though it's nice for debugging + { "$elementTypeAlias", elementTypeValidationResult.ElementTypeAlias } + }; + + var modelState = new ModelStateDictionary(); + + // loop over property validations + foreach (var propTypeResult in elementTypeValidationResult.ValidationResults) + { + // group the results by their type and iterate the groups + foreach (var result in propTypeResult.ValidationResults.GroupBy(x => x.GetType())) + { + // if the group's type isn't ComplexEditorValidationResult then it will in 99% of cases be + // just ValidationResult for whcih we want to create the sub "ModelState" data. If it's not a normal + // ValidationResult it will still just be converted to normal ModelState. + + if (result.Key == typeof(ComplexEditorValidationResult)) + { + // if it's ComplexEditorValidationResult then there can only be one which is validated so just get the single + if (result.Any()) + { + var complexResult = result.Single(); + // recurse to get the validation result object + var obj = JToken.FromObject(complexResult, camelCaseSerializer); + joElementType.Add(propTypeResult.PropertyTypeAlias, obj); + + // For any nested property error we add the model state as empty state for that nested property + // NOTE: Instead of the empty validation message we could put in the translated + // "errors/propertyHasErrors" message, however I think that leaves for less flexibility since it could/should be + // up to the front-end validator to show whatever message it wants (if any) for an error indicating a nested property error. + // Will leave blank. + modelState.AddPropertyValidationError(new ValidationResult(string.Empty), propTypeResult.PropertyTypeAlias, _culture, _segment); + } + } + else + { + foreach (var v in result) + { + modelState.AddPropertyValidationError(v, propTypeResult.PropertyTypeAlias, _culture, _segment); + } + } + } + } + + if (modelState.Count > 0) + { + joElementType.Add("ModelState", JObject.FromObject(modelState.ToErrorDictionary())); + } + + joElementType.WriteTo(writer); + } + else + { + + if (validationResult is ContentPropertyValidationResult propertyValidationResult + && propertyValidationResult.ComplexEditorResults?.ValidationResults.Count > 0) + { + // recurse to write out the NestedValidationResults + var obj = JToken.FromObject(propertyValidationResult.ComplexEditorResults, camelCaseSerializer); + obj.WriteTo(writer); + } + + var jo = new JObject(); + if (!validationResult.ErrorMessage.IsNullOrWhiteSpace()) + { + var errObj = JToken.FromObject(validationResult.ErrorMessage, camelCaseSerializer); + jo.Add("errorMessage", errObj); + } + if (validationResult.MemberNames.Any()) + { + var memberNamesObj = JToken.FromObject(validationResult.MemberNames, camelCaseSerializer); + jo.Add("memberNames", memberNamesObj); + } + if (jo.HasValues) + jo.WriteTo(writer); + } + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs new file mode 100644 index 0000000000..f043c8e66e --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockEditorConverter.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + /// + /// Converts json block objects into + /// + public sealed class BlockEditorConverter + { + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IPublishedModelFactory _publishedModelFactory; + + public BlockEditorConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _publishedModelFactory = publishedModelFactory; + } + + public IPublishedElement ConvertToElement( + BlockItemData data, + PropertyCacheLevel referenceCacheLevel, bool preview) + { + // hack! we need to cast, we have no choice beacuse we cannot make breaking changes. + var publishedContentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content as IPublishedContentCache2; + if (publishedContentCache == null) + throw new InvalidOperationException("The published content cache is not " + typeof(IPublishedContentCache2)); + + // only convert element types - content types will cause an exception when PublishedModelFactory creates the model + var publishedContentType = publishedContentCache.GetContentType(data.ContentTypeKey); + if (publishedContentType == null || publishedContentType.IsElement == false) + return null; + + var propertyValues = data.RawPropertyValues; + + // Get the udi from the deserialized object. If this is empty we can fallback to checking the 'key' if there is one + var key = (data.Udi is GuidUdi gudi) ? gudi.Guid : Guid.Empty; + if (propertyValues.TryGetValue("key", out var keyo)) + Guid.TryParse(keyo.ToString(), out key); + + IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor); + element = _publishedModelFactory.CreateModel(element); + return element; + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs new file mode 100644 index 0000000000..25b22e1a9c --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -0,0 +1,134 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Blocks; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.ValueConverters; + +namespace Umbraco.Web.PropertyEditors.ValueConverters +{ + + [DefaultPropertyValueConverter(typeof(JsonValueConverter))] + public class BlockListPropertyValueConverter : PropertyValueConverterBase + { + private readonly IProfilingLogger _proflog; + private readonly BlockEditorConverter _blockConverter; + private readonly BlockListEditorDataConverter _blockListEditorDataConverter; + + public BlockListPropertyValueConverter(IProfilingLogger proflog, BlockEditorConverter blockConverter) + { + _proflog = proflog; + _blockConverter = blockConverter; + _blockListEditorDataConverter = new BlockListEditorDataConverter(); + } + + /// + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.BlockList); + + /// + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(BlockListModel); + + /// + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + /// + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) + { + return source?.ToString(); + } + + /// + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + { + // NOTE: The intermediate object is just a json string, we don't actually convert from source -> intermediate since source is always just a json string + + using (_proflog.DebugDuration($"ConvertPropertyToBlockList ({propertyType.DataType.Id})")) + { + var configuration = propertyType.DataType.ConfigurationAs(); + var blockConfigMap = configuration.Blocks.ToDictionary(x => x.ContentElementTypeKey); + var validSettingElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey).Where(x => x.HasValue).Distinct().ToList(); + + var contentPublishedElements = new Dictionary(); + var settingsPublishedElements = new Dictionary(); + + var layout = new List(); + + var value = (string)inter; + if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty; + + var converted = _blockListEditorDataConverter.Deserialize(value); + if (converted.BlockValue.ContentData.Count == 0) return BlockListModel.Empty; + + var blockListLayout = converted.Layout.ToObject>(); + + // convert the content data + foreach (var data in converted.BlockValue.ContentData) + { + if (!blockConfigMap.ContainsKey(data.ContentTypeKey)) continue; + + var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview); + if (element == null) continue; + contentPublishedElements[element.Key] = element; + } + // convert the settings data + foreach (var data in converted.BlockValue.SettingsData) + { + if (!validSettingElementTypes.Contains(data.ContentTypeKey)) continue; + + var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview); + if (element == null) continue; + settingsPublishedElements[element.Key] = element; + } + + // if there's no elements just return since if there's no data it doesn't matter what is stored in layout + if (contentPublishedElements.Count == 0) return BlockListModel.Empty; + + foreach (var layoutItem in blockListLayout) + { + // get the content reference + var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi; + if (!contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData)) + continue; + + // get the setting reference + IPublishedElement settingsData = null; + var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null; + if (settingGuidUdi != null) + settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData); + + if (!contentData.ContentType.TryGetKey(out var contentTypeKey)) + throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2)); + + if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig)) + continue; + + // this can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again + // we also ensure that the content type's match since maybe the settings type has been changed after this has been persisted. + if (settingsData != null) + { + if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey)) + throw new InvalidOperationException("The settings element type was not of type " + typeof(IPublishedContentType2)); + + if (!blockConfig.SettingsElementTypeKey.HasValue || settingsElementTypeKey != blockConfig.SettingsElementTypeKey) + settingsData = null; + } + + var layoutRef = new BlockListLayoutReference(contentGuidUdi, contentData, settingGuidUdi, settingsData); + layout.Add(layoutRef); + } + + var model = new BlockListModel(contentPublishedElements.Values, settingsPublishedElements.Values, layout); + return model; + } + } + + + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index 4a25049695..11b924552e 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -38,8 +38,8 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var contentTypes = propertyType.DataType.ConfigurationAs().ContentTypes; return contentTypes.Length == 1 - ? typeof (IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias)) - : typeof (IEnumerable); + ? typeof(IEnumerable<>).MakeGenericType(ModelType.For(contentTypes[0].Alias)) + : typeof(IEnumerable); } /// diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs index c9c99615f6..c9859c9770 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentSingleValueConverter.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { using (_proflog.DebugDuration($"ConvertPropertyToNestedContent ({propertyType.DataType.Id})")) { - var value = (string) inter; + var value = (string)inter; if (string.IsNullOrWhiteSpace(value)) return null; var objects = JsonConvert.DeserializeObject>(value); diff --git a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs index 0370088f77..4760082908 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedCache.cs @@ -7,6 +7,16 @@ using Umbraco.Core.Xml; namespace Umbraco.Web.PublishedCache { + public interface IPublishedCache2 : IPublishedCache + { + /// + /// Gets a content type identified by its alias. + /// + /// The content type key. + /// The content type, or null. + IPublishedContentType GetContentType(Guid key); + } + /// /// Provides access to cached contents. /// diff --git a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs index b4a6e3d1e0..8175285c3a 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs @@ -4,6 +4,11 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.PublishedCache { + public interface IPublishedContentCache2 : IPublishedContentCache, IPublishedCache2 + { + // NOTE: this is here purely to avoid API breaking changes + } + public interface IPublishedContentCache : IPublishedCache { /// diff --git a/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs index 0b461882b7..702b4fe49d 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedMediaCache.cs @@ -1,5 +1,10 @@ namespace Umbraco.Web.PublishedCache { + public interface IPublishedMediaCache2 : IPublishedMediaCache, IPublishedCache2 + { + // NOTE: this is here purely to avoid API breaking changes + } + public interface IPublishedMediaCache : IPublishedCache { } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 24c6a7018b..8e6e517aea 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -13,7 +13,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable; namespace Umbraco.Web.PublishedCache.NuCache { - internal class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigableData, IDisposable + internal class ContentCache : PublishedCacheBase, IPublishedContentCache2, INavigableData, IDisposable { private readonly ContentStore.Snapshot _snapshot; private readonly IAppCache _snapshotCache; @@ -384,15 +384,11 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public override IPublishedContentType GetContentType(int id) - { - return _snapshot.GetContentType(id); - } + public override IPublishedContentType GetContentType(int id) => _snapshot.GetContentType(id); - public override IPublishedContentType GetContentType(string alias) - { - return _snapshot.GetContentType(alias); - } + public override IPublishedContentType GetContentType(string alias) => _snapshot.GetContentType(alias); + + public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key); #endregion diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index a3f918c92c..b39b38ca32 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -37,9 +37,14 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IVariationContextAccessor _variationContextAccessor; private readonly ConcurrentDictionary> _contentNodes; private LinkedNode _root; - private readonly ConcurrentDictionary> _contentTypesById; + + // We must keep separate dictionaries for by id and by alias because we track these in snapshot/layers + // and it is possible that the alias of a content type can be different for the same id in another layer + // whereas the GUID -> INT cross reference can never be different + private readonly ConcurrentDictionary> _contentTypesById; private readonly ConcurrentDictionary> _contentTypesByAlias; - private readonly ConcurrentDictionary _xmap; + private readonly ConcurrentDictionary _contentTypeKeyToIdMap; + private readonly ConcurrentDictionary _contentKeyToIdMap; private readonly ILogger _logger; private BPlusTree _localDb; @@ -73,7 +78,8 @@ namespace Umbraco.Web.PublishedCache.NuCache _root = new LinkedNode(new ContentNode(), 0); _contentTypesById = new ConcurrentDictionary>(); _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); - _xmap = new ConcurrentDictionary(); + _contentTypeKeyToIdMap = new ConcurrentDictionary(); + _contentKeyToIdMap = new ConcurrentDictionary(); _genObjs = new ConcurrentQueue(); _genObj = null; // no initial gen exists @@ -136,7 +142,7 @@ namespace Umbraco.Web.PublishedCache.NuCache Monitor.Enter(_wlocko, ref lockInfo.Taken); - lock(_rlocko) + lock (_rlocko) { // see SnapDictionary try { } @@ -152,7 +158,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _nextGen = true; } } - } + } } private void Release(WriteLockInfo lockInfo, bool commit = true) @@ -291,8 +297,7 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var type in types) { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); + SetContentTypeLocked(type); } } @@ -318,8 +323,7 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var type in index.Values) { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); + SetContentTypeLocked(type); } foreach (var link in _contentNodes.Values) @@ -354,8 +358,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // set all new content types foreach (var type in types) { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); + SetContentTypeLocked(type); } // beware! at that point the cache is inconsistent, @@ -419,8 +422,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // perform update of refreshed content types foreach (var type in refreshedTypesA) { - SetValueLocked(_contentTypesById, type.Id, type); - SetValueLocked(_contentTypesByAlias, type.Alias, type); + SetContentTypeLocked(type); } // perform update of content with refreshed content type - from the kits @@ -638,7 +640,7 @@ namespace Umbraco.Web.PublishedCache.NuCache kit.Node.PreviousSiblingContentId = existing.PreviousSiblingContentId; } - _xmap[kit.Node.Uid] = kit.Node.Id; + _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; return true; } @@ -734,7 +736,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // this node becomes the previous node previousNode = thisNode; - _xmap[kit.Node.Uid] = kit.Node.Id; + _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } return ok; @@ -757,7 +759,7 @@ namespace Umbraco.Web.PublishedCache.NuCache EnsureLocked(); var ok = true; - + ClearLocked(_contentNodes); ClearRootLocked(); @@ -778,7 +780,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (_localDb != null) RegisterChange(kit.Node.Id, kit); AddTreeNodeLocked(kit.Node, parent); - _xmap[kit.Node.Uid] = kit.Node.Id; + _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } return ok; @@ -807,7 +809,7 @@ namespace Umbraco.Web.PublishedCache.NuCache EnsureLocked(); var ok = true; - + // get existing _contentNodes.TryGetValue(rootContentId, out var link); var existing = link?.Value; @@ -833,7 +835,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (_localDb != null) RegisterChange(kit.Node.Id, kit); AddTreeNodeLocked(kit.Node, parent); - _xmap[kit.Node.Uid] = kit.Node.Id; + _contentKeyToIdMap[kit.Node.Uid] = kit.Node.Id; } return ok; @@ -885,11 +887,11 @@ namespace Umbraco.Web.PublishedCache.NuCache // This should never be null, all code that calls this method is null checking but we've seen // issues of null ref exceptions in issue reports so we'll double check here if (content == null) throw new ArgumentNullException(nameof(content)); - + SetValueLocked(_contentNodes, content.Id, null); if (_localDb != null) RegisterChange(content.Id, ContentNodeKit.Null); - _xmap.TryRemove(content.Uid, out _); + _contentKeyToIdMap.TryRemove(content.Uid, out _); var id = content.FirstChildContentId; while (id > 0) @@ -913,10 +915,10 @@ namespace Umbraco.Web.PublishedCache.NuCache { if (_contentNodes.TryGetValue(id, out var link)) { - link = GetLinkedNodeGen(link, gen); + link = GetLinkedNodeGen(link, gen); if (link != null && link.Value != null) return link; - } + } throw new PanicException($"failed to get {description} with id={id}"); } @@ -929,13 +931,13 @@ namespace Umbraco.Web.PublishedCache.NuCache { if (content.ParentContentId < 0) { - var root = GetLinkedNodeGen(_root, gen); + var root = GetLinkedNodeGen(_root, gen); return root; } if (_contentNodes.TryGetValue(content.ParentContentId, out var link)) link = GetLinkedNodeGen(link, gen); - + return link; } @@ -1154,6 +1156,15 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + private void SetContentTypeLocked(IPublishedContentType type) + { + SetValueLocked(_contentTypesById, type.Id, type); + SetValueLocked(_contentTypesByAlias, type.Alias, type); + // ensure the key/id map is accurate + if (type.TryGetKey(out var key)) + _contentTypeKeyToIdMap[key] = type.Id; + } + // set a node (just the node, not the tree) private void SetValueLocked(ConcurrentDictionary> dict, TKey key, TValue value) where TValue : class @@ -1211,14 +1222,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public ContentNode Get(Guid uid, long gen) { - return _xmap.TryGetValue(uid, out var id) + return _contentKeyToIdMap.TryGetValue(uid, out var id) ? GetValue(_contentNodes, id, gen) : null; } public IEnumerable GetAtRoot(long gen) { - var root = GetLinkedNodeGen(_root, gen); + var root = GetLinkedNodeGen(_root, gen); if (root == null) yield break; @@ -1274,13 +1285,20 @@ namespace Umbraco.Web.PublishedCache.NuCache return GetValue(_contentTypesByAlias, alias, gen); } + public IPublishedContentType GetContentType(Guid key, long gen) + { + if (!_contentTypeKeyToIdMap.TryGetValue(key, out var id)) + return null; + return GetContentType(id, gen); + } + #endregion #region Snapshots public Snapshot CreateSnapshot() { - lock(_rlocko) + lock (_rlocko) { // if no next generation is required, and we already have one, // use it and create a new snapshot @@ -1606,6 +1624,13 @@ namespace Umbraco.Web.PublishedCache.NuCache return _store.GetContentType(alias, _gen); } + public IPublishedContentType GetContentType(Guid key) + { + if (_gen < 0) + throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/); + return _store.GetContentType(key, _gen); + } + // this code is here just so you don't try to implement it // the only way we can iterate over "all" without locking the entire cache forever // is by shallow cloning the cache, which is quite expensive, so we should probably not do it, diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index 694dac04df..f62014a368 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -208,7 +208,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource if (dto.EditData == null) { if (Debugger.IsAttached) - throw new Exception("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); + throw new InvalidOperationException("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); Current.Logger.Warn("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id); } else @@ -235,7 +235,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource if (dto.PubData == null) { if (Debugger.IsAttached) - throw new Exception("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); + throw new InvalidOperationException("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); Current.Logger.Warn("Missing cmsContentNu published content for node {NodeId}, consider rebuilding.", dto.Id); } else @@ -274,7 +274,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource private static ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto) { if (dto.EditData == null) - throw new Exception("No data for media " + dto.Id); + throw new InvalidOperationException("No data for media " + dto.Id); var nested = DeserializeNestedData(dto.EditData); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs index 182086ed7f..a466460ede 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/MediaCache.cs @@ -11,7 +11,7 @@ using Umbraco.Web.PublishedCache.NuCache.Navigable; namespace Umbraco.Web.PublishedCache.NuCache { - internal class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableData, IDisposable + internal class MediaCache : PublishedCacheBase, IPublishedMediaCache2, INavigableData, IDisposable { private readonly ContentStore.Snapshot _snapshot; private readonly IVariationContextAccessor _variationContextAccessor; @@ -155,15 +155,11 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Content types - public override IPublishedContentType GetContentType(int id) - { - return _snapshot.GetContentType(id); - } + public override IPublishedContentType GetContentType(int id) => _snapshot.GetContentType(id); - public override IPublishedContentType GetContentType(string alias) - { - return _snapshot.GetContentType(alias); - } + public override IPublishedContentType GetContentType(string alias) => _snapshot.GetContentType(alias); + + public override IPublishedContentType GetContentType(Guid key) => _snapshot.GetContentType(key); #endregion diff --git a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs index 1f637663e5..1b4a9bb92a 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedCacheBase.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Xml; namespace Umbraco.Web.PublishedCache { - abstract class PublishedCacheBase : IPublishedCache + internal abstract class PublishedCacheBase : IPublishedCache2 { public bool PreviewDefault { get; } @@ -89,8 +89,8 @@ namespace Umbraco.Web.PublishedCache } public abstract IPublishedContentType GetContentType(int id); - public abstract IPublishedContentType GetContentType(string alias); + public abstract IPublishedContentType GetContentType(Guid key); public virtual IEnumerable GetByContentType(IPublishedContentType contentType) { diff --git a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs index e453471bb8..8eb50b0588 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedContentTypeCache.cs @@ -15,8 +15,10 @@ namespace Umbraco.Web.PublishedCache /// This cache is not snapshotted, so it refreshes any time things change. public class PublishedContentTypeCache { + // NOTE: These are not concurrent dictionaries because all access is done within a lock private readonly Dictionary _typesByAlias = new Dictionary(); private readonly Dictionary _typesById = new Dictionary(); + private readonly Dictionary _keyToIdMap = new Dictionary(); private readonly IContentTypeService _contentTypeService; private readonly IMediaTypeService _mediaTypeService; private readonly IMemberTypeService _memberTypeService; @@ -130,6 +132,42 @@ namespace Umbraco.Web.PublishedCache } } + /// + /// Gets a published content type. + /// + /// An item type. + /// An key. + /// The published content type corresponding to the item key. + public IPublishedContentType Get(PublishedItemType itemType, Guid key) + { + try + { + _lock.EnterUpgradeableReadLock(); + + if (_keyToIdMap.TryGetValue(key, out var id)) + return Get(itemType, id); + + var type = CreatePublishedContentType(itemType, key); + + try + { + _lock.EnterWriteLock(); + _keyToIdMap[key] = type.Id; + return _typesByAlias[GetAliasKey(type)] = _typesById[type.Id] = type; + } + finally + { + if (_lock.IsWriteLockHeld) + _lock.ExitWriteLock(); + } + } + finally + { + if (_lock.IsUpgradeableReadLockHeld) + _lock.ExitUpgradeableReadLock(); + } + } + /// /// Gets a published content type. /// @@ -152,7 +190,8 @@ namespace Umbraco.Web.PublishedCache try { _lock.EnterWriteLock(); - + if (type.TryGetKey(out var key)) + _keyToIdMap[key] = type.Id; return _typesByAlias[aliasKey] = _typesById[type.Id] = type; } finally @@ -188,7 +227,8 @@ namespace Umbraco.Web.PublishedCache try { _lock.EnterWriteLock(); - + if (type.TryGetKey(out var key)) + _keyToIdMap[key] = type.Id; return _typesByAlias[GetAliasKey(type)] = _typesById[type.Id] = type; } finally @@ -204,27 +244,32 @@ namespace Umbraco.Web.PublishedCache } } + private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, Guid key) + { + IContentTypeComposition contentType = itemType switch + { + PublishedItemType.Content => _contentTypeService.Get(key), + PublishedItemType.Media => _mediaTypeService.Get(key), + PublishedItemType.Member => _memberTypeService.Get(key), + _ => throw new ArgumentOutOfRangeException(nameof(itemType)), + }; + if (contentType == null) + throw new Exception($"ContentTypeService failed to find a {itemType.ToString().ToLower()} type with key \"{key}\"."); + + return _publishedContentTypeFactory.CreateContentType(contentType); + } + private IPublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) { if (GetPublishedContentTypeByAlias != null) return GetPublishedContentTypeByAlias(alias); - - IContentTypeComposition contentType; - switch (itemType) + IContentTypeComposition contentType = itemType switch { - case PublishedItemType.Content: - contentType = _contentTypeService.Get(alias); - break; - case PublishedItemType.Media: - contentType = _mediaTypeService.Get(alias); - break; - case PublishedItemType.Member: - contentType = _memberTypeService.Get(alias); - break; - default: - throw new ArgumentOutOfRangeException(nameof(itemType)); - } - + PublishedItemType.Content => _contentTypeService.Get(alias), + PublishedItemType.Media => _mediaTypeService.Get(alias), + PublishedItemType.Member => _memberTypeService.Get(alias), + _ => throw new ArgumentOutOfRangeException(nameof(itemType)), + }; if (contentType == null) throw new Exception($"ContentTypeService failed to find a {itemType.ToString().ToLower()} type with alias \"{alias}\"."); @@ -235,23 +280,13 @@ namespace Umbraco.Web.PublishedCache { if (GetPublishedContentTypeById != null) return GetPublishedContentTypeById(id); - - IContentTypeComposition contentType; - switch (itemType) + IContentTypeComposition contentType = itemType switch { - case PublishedItemType.Content: - contentType = _contentTypeService.Get(id); - break; - case PublishedItemType.Media: - contentType = _mediaTypeService.Get(id); - break; - case PublishedItemType.Member: - contentType = _memberTypeService.Get(id); - break; - default: - throw new ArgumentOutOfRangeException(nameof(itemType)); - } - + PublishedItemType.Content => _contentTypeService.Get(id), + PublishedItemType.Media => _mediaTypeService.Get(id), + PublishedItemType.Member => _memberTypeService.Get(id), + _ => throw new ArgumentOutOfRangeException(nameof(itemType)), + }; if (contentType == null) throw new Exception($"ContentTypeService failed to find a {itemType.ToString().ToLower()} type with id {id}."); @@ -259,6 +294,7 @@ namespace Umbraco.Web.PublishedCache } // for unit tests - changing the callback must reset the cache obviously + // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id private Func _getPublishedContentTypeByAlias; internal Func GetPublishedContentTypeByAlias { @@ -282,6 +318,7 @@ namespace Umbraco.Web.PublishedCache } // for unit tests - changing the callback must reset the cache obviously + // TODO: Why does this even exist? For testing you'd pass in a mocked service to get by id private Func _getPublishedContentTypeById; internal Func GetPublishedContentTypeById { diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 60521e6d90..135d54560b 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -110,6 +110,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); // register the umbraco helper - this is Transient! very important! // also, if not level.Run, we cannot really use the helper (during upgrade...) diff --git a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs index 746bc61e34..9a4a4f0e2c 100644 --- a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs +++ b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs @@ -5,6 +5,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; using Umbraco.Core.Sync; using Umbraco.Web.HealthCheck; @@ -15,16 +16,17 @@ namespace Umbraco.Web.Scheduling private readonly IRuntimeState _runtimeState; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; + private readonly IScopeProvider _scopeProvider; private readonly IProfilingLogger _logger; public HealthCheckNotifier(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, - IRuntimeState runtimeState, - IProfilingLogger logger) + IScopeProvider scopeProvider, IRuntimeState runtimeState, IProfilingLogger logger) : base(runner, delayMilliseconds, periodMilliseconds) { _healthChecks = healthChecks; _notifications = notifications; + _scopeProvider = scopeProvider; _runtimeState = runtimeState; _logger = logger; } @@ -51,6 +53,10 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, going down } + // Ensure we use an explicit scope since we are running on a background thread and plugin health + // checks can be making service/database calls so we want to ensure the CallContext/Ambient scope + // isn't used since that can be problematic. + using (var scope = _scopeProvider.CreateScope()) using (_logger.DebugDuration("Health checks executing", "Health checks complete")) { var healthCheckConfig = Current.Configs.HealthChecks(); diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index db13a80f9b..ffdb584c7a 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -70,8 +70,7 @@ namespace Umbraco.Web.Scheduling return false; // do NOT repeat, going down } - // running on a background task, and Log.CleanLogs uses the old SqlHelper, - // better wrap in a scope and ensure it's all cleaned up and nothing leaks + // Ensure we use an explicit scope since we are running on a background thread. using (var scope = _scopeProvider.CreateScope()) using (_logger.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index 2e79e40d7a..97afe25e22 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -2,6 +2,7 @@ using System.Linq; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Sync; @@ -55,30 +56,24 @@ namespace Umbraco.Web.Scheduling try { - // ensure we run with an UmbracoContext, because this may run in a background task, - // yet developers may be using the 'current' UmbracoContext in the event handlers - // - // TODO: or maybe not, CacheRefresherComponent already ensures a context when handling events - // - UmbracoContext 'current' needs to be refactored and cleaned up - // - batched messenger should not depend on a current HttpContext - // but then what should be its "scope"? could we attach it to scopes? - // - and we should definitively *not* have to flush it here (should be auto) - // - using (var contextReference = _umbracoContextFactory.EnsureUmbracoContext()) + // We don't need an explicit scope here because PerformScheduledPublish creates it's own scope + // so it's safe as it will create it's own ambient scope. + // Ensure we run with an UmbracoContext, because this will run in a background task, + // and developers may be using the UmbracoContext in the event handlers. + + using var contextReference = _umbracoContextFactory.EnsureUmbracoContext(); + try { - try - { - // run - var result = _contentService.PerformScheduledPublish(DateTime.Now); - foreach (var grouped in result.GroupBy(x => x.Result)) - _logger.Info("Scheduled publishing result: '{StatusCount}' items with status {Status}", grouped.Count(), grouped.Key); - } - finally - { - // if running on a temp context, we have to flush the messenger - if (contextReference.IsRoot && Composing.Current.ServerMessenger is BatchedDatabaseServerMessenger m) - m.FlushBatch(); - } + // run + var result = _contentService.PerformScheduledPublish(DateTime.Now); + foreach (var grouped in result.GroupBy(x => x.Result)) + _logger.Info("Scheduled publishing result: '{StatusCount}' items with status {Status}", grouped.Count(), grouped.Key); + } + finally + { + // if running on a temp context, we have to flush the messenger + if (contextReference.IsRoot && Composing.Current.ServerMessenger is BatchedDatabaseServerMessenger m) + m.FlushBatch(); } } catch (Exception ex) diff --git a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs index 55ae52a05b..dbd82cc899 100644 --- a/src/Umbraco.Web/Scheduling/SchedulerComponent.cs +++ b/src/Umbraco.Web/Scheduling/SchedulerComponent.cs @@ -155,7 +155,7 @@ namespace Umbraco.Web.Scheduling } var periodInMilliseconds = healthCheckConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; - var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _runtime, logger); + var task = new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, healthChecks, notifications, _scopeProvider, _runtime, logger); _healthCheckRunner.TryAdd(task); return task; } diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index f74897d565..01044dd7f2 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -77,6 +77,17 @@ namespace Umbraco.Web.Security return _publicAccessService.IsProtected(path); } + public virtual IDictionary IsProtected(IEnumerable paths) + { + var result = new Dictionary(); + foreach (var path in paths) + { + //this is a cached call + result[path] = _publicAccessService.IsProtected(path); + } + return result; + } + /// /// Check if the current user has access to a document /// @@ -84,15 +95,33 @@ namespace Umbraco.Web.Security /// True if the current user has access or if the current document isn't protected public virtual bool MemberHasAccess(string path) { - //cache this in the request cache - return _appCaches.RequestCache.GetCacheItem($"{typeof(MembershipHelper)}.MemberHasAccess-{path}", () => + if (IsProtected(path)) { - if (IsProtected(path)) - { - return IsLoggedIn() && HasAccess(path, Roles.Provider); - } - return true; - }); + return IsLoggedIn() && HasAccess(path, Roles.Provider); + } + return true; + } + + /// + /// Checks if the current user has access to the paths + /// + /// + /// + public virtual IDictionary MemberHasAccess(IEnumerable paths) + { + var protectedPaths = IsProtected(paths); + + var pathsWithProtection = protectedPaths.Where(x => x.Value).Select(x => x.Key); + var pathsWithAccess = HasAccess(pathsWithProtection, Roles.Provider); + + var result = new Dictionary(); + foreach(var path in paths) + { + pathsWithAccess.TryGetValue(path, out var hasAccess); + // if it's not found it's false anyways + result[path] = hasAccess; + } + return result; } /// @@ -106,6 +135,25 @@ namespace Umbraco.Web.Security return _publicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser); } + private IDictionary HasAccess(IEnumerable paths, RoleProvider roleProvider) + { + // ensure we only lookup user roles once + string[] userRoles = null; + string[] getUserRoles(string username) + { + if (userRoles != null) return userRoles; + userRoles = roleProvider.GetRolesForUser(username).ToArray(); + return userRoles; + } + + var result = new Dictionary(); + foreach (var path in paths) + { + result[path] = IsLoggedIn() && _publicAccessService.HasAccess(path, CurrentUserName, getUserRoles); + } + return result; + } + /// /// Returns true if the current membership provider is the Umbraco built-in one. /// @@ -796,7 +844,7 @@ namespace Umbraco.Web.Security private static string GetCacheKey(string key, params object[] additional) { var sb = new StringBuilder(); - sb.Append(typeof (MembershipHelper).Name); + sb.Append(typeof(MembershipHelper).Name); sb.Append("-"); sb.Append(key); foreach (var s in additional) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj old mode 100755 new mode 100644 index 2ca7d6d7ae..b2068290f6 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -124,6 +124,7 @@ + @@ -156,6 +157,7 @@ + @@ -247,9 +249,21 @@ + + + + + + + + + + + + diff --git a/src/umbraco.sln b/src/umbraco.sln index 63fb856b5d..78ff0ef12d 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -58,8 +58,24 @@ 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}" +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "Umbraco.Tests.AcceptanceTest", "Umbraco.Tests.AcceptanceTest\", "{9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}" ProjectSection(WebsiteProperties) = preProject + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" + Debug.AspNetCompiler.VirtualPath = "/localhost_62926" + Debug.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" + Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" + Debug.AspNetCompiler.Updateable = "true" + Debug.AspNetCompiler.ForceOverwrite = "true" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.Debug = "True" + Release.AspNetCompiler.VirtualPath = "/localhost_62926" + Release.AspNetCompiler.PhysicalPath = "Umbraco.Tests.AcceptanceTest\" + Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_62926\" + Release.AspNetCompiler.Updateable = "true" + Release.AspNetCompiler.ForceOverwrite = "true" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.Debug = "False" + VWDPort = "62926" SlnRelativePath = "Umbraco.Tests.AcceptanceTest\" EndProjectSection EndProject @@ -123,6 +139,9 @@ Global {4C4C194C-B5E4-4991-8F87-4373E24CC19F}.Release|Any CPU.Build.0 = Release|Any CPU {3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3819A550-DCEC-4153-91B4-8BA9F7F0B9B4}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817}.Release|Any CPU.Build.0 = Debug|Any CPU {651E1350-91B6-44B7-BD60-7207006D7003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {651E1350-91B6-44B7-BD60-7207006D7003}.Debug|Any CPU.Build.0 = Debug|Any CPU {651E1350-91B6-44B7-BD60-7207006D7003}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -157,6 +176,7 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {227C3B55-80E5-4E7E-A802-BE16C5128B9D} = {2849E9D4-3B4E-40A3-A309-F3CB4F0E125F} + {9E4C8A12-FBE0-4673-8CE2-DF99D5D57817} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {5D3B8245-ADA6-453F-A008-50ED04BFE770} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {E3F9F378-AFE1-40A5-90BD-82833375DBFE} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D} {5B03EF4E-E0AC-4905-861B-8C3EC1A0D458} = {227C3B55-80E5-4E7E-A802-BE16C5128B9D} @@ -164,7 +184,6 @@ Global {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} {FB5676ED-7A69-492C-B802-E7B24144C0FC} = {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}